footprint.py python
144 lines 4.7 KB
Raw
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 54 days ago
1 """Vendored footprint resolution per §K4.5."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6 from pathlib import Path
7
8 from adapters.config import OverseerConfig
9 from adapters.errors import ConfigError
10 from adapters.templating import render_template
11 from cli.docs_paths import join_docs_rel
12 from cli.kit_root import kit_root
13
14 MUSE_BRIDGE_WORKFLOW_DEST = "MUSE-BRIDGE-WORKFLOW.md"
15 MUSE_BRIDGE_DEPLOY_DEST = "scripts/muse-bridge-deploy.sh"
16 EXECUTABLE_FOOTPRINT_DESTINATIONS = frozenset({MUSE_BRIDGE_DEPLOY_DEST})
17
18
19 @dataclass(frozen=True)
20 class FootprintFile:
21 """One vendored footprint file (destination path, kit source, rendered bytes)."""
22
23 destination: str
24 source: str
25 content: bytes
26
27 @property
28 def text(self) -> str:
29 return self.content.decode("utf-8")
30
31
32 def _docs_path(config: OverseerConfig, doc_name: str) -> str:
33 return join_docs_rel(config.repo.root_relative_docs, doc_name)
34
35
36 def resolve_footprint(config: OverseerConfig, *, kit: Path | None = None) -> list[FootprintFile]:
37 """Resolve the full footprint for ``config``; fail closed on destination collisions."""
38 root = kit or kit_root()
39 files: list[FootprintFile] = []
40
41 template_specs: list[tuple[str, str]] = [
42 ("templates/OVERSEER-HANDOVER.template.md", _docs_path(config, config.docs.handover)),
43 ("templates/ROADMAP.template.md", _docs_path(config, config.docs.roadmap)),
44 (
45 "templates/STANDING-DECISIONS.template.md",
46 ".overseer/STANDING-DECISIONS.reference.md",
47 ),
48 ]
49 if config.docs.coordination:
50 template_specs.append(
51 (
52 "templates/CROSS-REPO-COORDINATION.template.md",
53 _docs_path(config, config.docs.coordination),
54 )
55 )
56
57 if config.vcs.regime == "muse+git-mirror":
58 template_specs.extend(
59 [
60 (
61 "templates/MUSE-BRIDGE-WORKFLOW.template.md",
62 MUSE_BRIDGE_WORKFLOW_DEST,
63 ),
64 (
65 "templates/scripts/muse-bridge-deploy.sh.template",
66 MUSE_BRIDGE_DEPLOY_DEST,
67 ),
68 ]
69 )
70
71 destinations: set[str] = set()
72 for source_rel, dest in template_specs:
73 if dest in destinations:
74 raise ConfigError(
75 f"duplicate footprint destination {dest!r} (config collision)",
76 None,
77 )
78 destinations.add(dest)
79 template_path = root / source_rel
80 rendered = render_template(template_path, config)
81 files.append(
82 FootprintFile(
83 destination=dest,
84 source=source_rel,
85 content=rendered.encode("utf-8"),
86 )
87 )
88
89 policy_dir = root / "policy"
90 for src in sorted(policy_dir.glob("*.yaml")):
91 dest = f".overseer/policy/{src.name}"
92 if dest in destinations:
93 raise ConfigError(f"duplicate footprint destination {dest!r}", None)
94 destinations.add(dest)
95 files.append(
96 FootprintFile(
97 destination=dest,
98 source=f"policy/{src.name}",
99 content=src.read_bytes(),
100 )
101 )
102
103 rules_dir = root / "cursor" / "rules"
104 if rules_dir.is_dir():
105 for src in sorted(rules_dir.iterdir()):
106 if not src.is_file():
107 continue
108 dest = f".cursor/rules/{src.name}"
109 if dest in destinations:
110 raise ConfigError(f"duplicate footprint destination {dest!r}", None)
111 destinations.add(dest)
112 files.append(
113 FootprintFile(
114 destination=dest,
115 source=f"cursor/rules/{src.name}",
116 content=src.read_bytes(),
117 )
118 )
119
120 skills_dir = root / "cursor" / "skills"
121 if skills_dir.is_dir():
122 for src in sorted(skills_dir.rglob("*")):
123 if not src.is_file():
124 continue
125 rel = src.relative_to(skills_dir)
126 dest = f".cursor/skills/{rel.as_posix()}"
127 if dest in destinations:
128 raise ConfigError(f"duplicate footprint destination {dest!r}", None)
129 destinations.add(dest)
130 files.append(
131 FootprintFile(
132 destination=dest,
133 source=f"cursor/skills/{rel.as_posix()}",
134 content=src.read_bytes(),
135 )
136 )
137
138 files.sort(key=lambda item: item.destination)
139 return files
140
141
142 def footprint_tuples(files: list[FootprintFile]) -> list[tuple[str, str, bytes]]:
143 """Return ``(destination, source, bytes)`` tuples for lock building."""
144 return [(f.destination, f.source, f.content) for f in files]
File History 1 commit
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 54 days ago