config_gen.py python
217 lines 7.0 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 9 hours ago
1 """Default config generation for ``overseer init``."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 import yaml
8
9 from adapters.config import SUPPORTED_CONFIG_VERSION, OverseerConfig, load_config
10 from adapters.errors import ConfigError
11
12
13 def detect_regime(repo_root: Path) -> str | None:
14 """Advisory regime detection from repo markers."""
15 has_git = (repo_root / ".git").exists()
16 has_muse = (repo_root / ".muse").is_dir()
17 has_bridge = (repo_root / ".muse" / "git-bridge.toml").is_file()
18 if has_bridge:
19 return "muse+git-mirror"
20 if has_muse:
21 return "muse-only"
22 if has_git:
23 return "git-only"
24 return None
25
26
27 def default_config_dict(
28 *,
29 regime: str,
30 repo_name: str,
31 docs_dir: str,
32 ) -> dict:
33 """Build a default config mapping for the given regime."""
34 from tools.workspace.board_names import (
35 expected_handover_basename,
36 expected_handover_title,
37 expected_roadmap_basename,
38 expected_roadmap_title,
39 )
40
41 docs_dir = docs_dir.strip("/") or "docs"
42 handover = expected_handover_basename(repo_name)
43 roadmap = expected_roadmap_basename(repo_name)
44 handover_title = expected_handover_title(repo_name)
45 roadmap_title = expected_roadmap_title(repo_name)
46 base = {
47 "overseer_config_version": SUPPORTED_CONFIG_VERSION,
48 "repo": {
49 "name": repo_name,
50 "root_relative_docs": docs_dir,
51 },
52 "thresholds": {
53 "realign_max_commits": 50,
54 "drift_warn_only": True,
55 },
56 "freeze_contract": {
57 "enabled": True,
58 "reviewer": {
59 "mode": "agent",
60 "model": "thinking-high",
61 "provider": "local",
62 "fallback": "human",
63 },
64 "human_escalation": ["security"],
65 },
66 }
67
68 if regime == "git-only":
69 base["vcs"] = {
70 "regime": "git-only",
71 "canonical": "git",
72 "git": {
73 "remote": "origin",
74 "main_branch": "main",
75 "mirror_branch": None,
76 "feature_branch_pattern": "feat/{slug}",
77 },
78 "muse": {
79 "staging_remote": None,
80 "main_branch": None,
81 },
82 }
83 base["docs"] = {
84 "handover": handover,
85 "roadmap": roadmap,
86 "coordination": None,
87 "standing_decisions": roadmap,
88 "handover_title": handover_title,
89 "roadmap_title": roadmap_title,
90 }
91 return base
92
93 if regime == "muse-only":
94 base["vcs"] = {
95 "regime": "muse-only",
96 "canonical": "muse",
97 "git": {
98 "remote": "origin",
99 "main_branch": "main",
100 "mirror_branch": None,
101 "feature_branch_pattern": "feat/{slug}",
102 },
103 "muse": {
104 "staging_remote": None,
105 "main_branch": "main",
106 },
107 }
108 base["docs"] = {
109 "handover": handover,
110 "roadmap": roadmap,
111 "coordination": None,
112 "standing_decisions": roadmap,
113 "handover_title": handover_title,
114 "roadmap_title": roadmap_title,
115 }
116 return base
117
118 if regime == "muse+git-mirror":
119 base["vcs"] = {
120 "regime": "muse+git-mirror",
121 "canonical": "muse",
122 "git": {
123 "remote": "origin",
124 "main_branch": "main",
125 "mirror_branch": "muse-mirror",
126 "feature_branch_pattern": "feat/{slug}",
127 },
128 "muse": {
129 "staging_remote": "staging",
130 "main_branch": "main",
131 },
132 }
133 base["docs"] = {
134 "handover": handover,
135 "roadmap": roadmap,
136 "coordination": "CROSS-REPO-COORDINATION.md",
137 "standing_decisions": "CROSS-REPO-COORDINATION.md",
138 "handover_title": handover_title,
139 "roadmap_title": roadmap_title,
140 }
141 return base
142
143 raise ConfigError(f"unsupported regime {regime!r}")
144
145
146 def config_dict_to_yaml(data: dict) -> str:
147 """Serialize a config mapping to YAML text."""
148 return yaml.safe_dump(data, sort_keys=False, allow_unicode=True, default_flow_style=False)
149
150
151 def load_config_from_dict(data: dict, path: str) -> OverseerConfig:
152 """Validate a config dict via a temporary path label."""
153 return load_config_from_text(config_dict_to_yaml(data), path)
154
155
156 def load_config_from_text(text: str, path: str) -> OverseerConfig:
157 """Parse config text by writing to a temp file for ``load_config`` validation."""
158 from tempfile import NamedTemporaryFile
159
160 with NamedTemporaryFile("w", suffix=".yaml", delete=False, encoding="utf-8") as handle:
161 handle.write(text)
162 temp_path = Path(handle.name)
163 try:
164 return load_config(temp_path)
165 finally:
166 temp_path.unlink(missing_ok=True)
167
168
169 def configs_equal(a: OverseerConfig, b: OverseerConfig) -> bool:
170 """Return True when two validated configs are equivalent."""
171 return config_dict_to_yaml(config_to_dict(a)) == config_dict_to_yaml(config_to_dict(b))
172
173
174 def config_to_dict(config: OverseerConfig) -> dict:
175 """Convert ``OverseerConfig`` back to a YAML-compatible mapping."""
176 return {
177 "overseer_config_version": config.overseer_config_version,
178 "repo": {
179 "name": config.repo.name,
180 "root_relative_docs": config.repo.root_relative_docs,
181 },
182 "vcs": {
183 "regime": config.vcs.regime,
184 "canonical": config.vcs.canonical,
185 "git": {
186 "remote": config.vcs.git.remote,
187 "main_branch": config.vcs.git.main_branch,
188 "mirror_branch": config.vcs.git.mirror_branch,
189 "feature_branch_pattern": config.vcs.git.feature_branch_pattern,
190 },
191 "muse": {
192 "staging_remote": config.vcs.muse.staging_remote,
193 "main_branch": config.vcs.muse.main_branch,
194 "working_dir": config.vcs.muse.working_dir,
195 },
196 },
197 "docs": {
198 "handover": config.docs.handover,
199 "roadmap": config.docs.roadmap,
200 "coordination": config.docs.coordination,
201 "standing_decisions": config.docs.standing_decisions,
202 },
203 "thresholds": {
204 "realign_max_commits": config.thresholds.realign_max_commits,
205 "drift_warn_only": config.thresholds.drift_warn_only,
206 },
207 "freeze_contract": {
208 "enabled": config.freeze_contract.enabled,
209 "reviewer": {
210 "mode": config.freeze_contract.reviewer.mode,
211 "model": config.freeze_contract.reviewer.model,
212 "provider": config.freeze_contract.reviewer.provider,
213 "fallback": config.freeze_contract.reviewer.fallback,
214 },
215 "human_escalation": list(config.freeze_contract.human_escalation),
216 },
217 }
File History 2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 9 hours ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 52 days ago