review.py python
225 lines 7.8 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 2 days ago
1 """``overseer review --freeze`` command (§K5.2)."""
2
3 from __future__ import annotations
4
5 import sys
6 from argparse import Namespace
7 from pathlib import Path
8
9 from adapters.config import load_config
10 from adapters.errors import ConfigError, ReadError
11 from adapters.factory import create_adapter
12 from cli.context import CliContext
13 from cli.kit_root import kit_version
14 from cli.paths import PathEscapeError, confine_path, repo_relative, resolve_config_path, resolve_repo_root
15 from cli.sanitize import format_config_error
16 from tools.freeze_reviewer.checklist import builtin_checklist, load_checklist_file
17 from tools.freeze_reviewer.engine import ReviewOptions, resolve_exit_code, resolve_reviewer_settings, run_freeze_review
18 from tools.freeze_reviewer.labels import validate_reviewer_model
19 from tools.footprint_coverage import check_footprint_coverage
20 from tools.footprint_integrity import check_footprint_integrity
21 from tools.freeze_reviewer.report import build_report, render_human_report
22 from tools.muse_sync import check_muse_sync
23 from tools.substrate_health import check_substrate
24
25 DISALLOWED_FLAGS = frozenset(
26 {
27 "--write-vcs",
28 "--commit",
29 "--push",
30 "--escalate-force-pass",
31 }
32 )
33
34
35 def _validate_raw_argv(argv: list[str]) -> int | None:
36 """Reject disallowed flags with USAGE exit 1."""
37 for token in argv:
38 if token in DISALLOWED_FLAGS:
39 return 1
40 if token.startswith("--model") and "=" in token:
41 value = token.split("=", 1)[1]
42 if _looks_like_vendor_slug(value):
43 return 1
44 if token == "--model":
45 return None
46 return None
47
48
49 def _looks_like_vendor_slug(value: str) -> bool:
50 lowered = value.lower()
51 return any(marker in lowered for marker in ("gpt-", "claude-", "composer-"))
52
53
54 def _resolve_effective_checklist(args: Namespace, repo_root: Path) -> tuple[list | None, int | None]:
55 if args.checklist is None:
56 return builtin_checklist(), None
57 try:
58 checklist_path = confine_path(repo_root, args.checklist)
59 except PathEscapeError:
60 return None, 4
61 if not checklist_path.is_file():
62 return None, 4
63 try:
64 return load_checklist_file(checklist_path), None
65 except ConfigError:
66 return None, 2
67
68
69 def _resolve_artifact(args: Namespace, repo_root: Path) -> tuple[Path | None, str | None, int | None]:
70 try:
71 artifact_path = confine_path(repo_root, args.freeze_path)
72 except PathEscapeError:
73 return None, None, 4
74 if not artifact_path.is_file():
75 return None, None, 4
76 rel = repo_relative(repo_root, artifact_path)
77 return artifact_path, rel, None
78
79
80 def run_review(args: Namespace, ctx: CliContext, *, raw_argv: list[str] | None = None) -> int:
81 """Execute ``overseer review --freeze``."""
82 argv = raw_argv or []
83 disallowed = _validate_raw_argv(argv)
84 if disallowed is not None:
85 ctx.output.error("usage: invalid or disallowed flag")
86 return disallowed
87
88 repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="review")
89 overseer_dir = repo_root / ".overseer"
90 if not overseer_dir.is_dir():
91 ctx.output.error("not initialized — run ok init first")
92 return 2
93
94 config_path = resolve_config_path(repo_root, args.config)
95 try:
96 config = load_config(config_path)
97 except ConfigError as exc:
98 ctx.output.error(format_config_error(exc, repo_root))
99 return 2
100
101 checklist, checklist_code = _resolve_effective_checklist(args, repo_root)
102 if checklist_code is not None:
103 if checklist_code == 2:
104 ctx.output.error("invalid checklist file")
105 else:
106 ctx.output.error("refused: checklist path")
107 return checklist_code
108
109 artifact_path, rel_path, artifact_code = _resolve_artifact(args, repo_root)
110 if artifact_code is not None or artifact_path is None or rel_path is None:
111 ctx.output.error("refused: artifact path")
112 return artifact_code or 4
113
114 # Validate model label when agent mode effective
115 effective_mode = args.mode or config.freeze_contract.reviewer.mode
116 effective_model = args.model or config.freeze_contract.reviewer.model
117 if effective_mode != "human":
118 if args.model and _looks_like_vendor_slug(args.model):
119 ctx.output.error("reviewer.model must be a label, not a vendor slug")
120 return 1
121 try:
122 validate_reviewer_model(effective_model, ctx.kit)
123 except ConfigError as exc:
124 ctx.output.error(format_config_error(exc, repo_root))
125 return 2
126 if args.provider and args.provider not in {"local", "api"}:
127 ctx.output.error("invalid --provider value")
128 return 1
129 if args.mode and args.mode not in {"agent", "human"}:
130 ctx.output.error("invalid --mode value")
131 return 1
132
133 adapter = create_adapter(config, repo_root, runner=ctx.runner)
134 substrate = check_substrate(config, repo_root)
135 if not substrate.ok:
136 ctx.output.error(f"substrate: {substrate.state} — {substrate.message}")
137 if substrate.remediation:
138 ctx.output.error(f"remediation: {substrate.remediation}")
139 return 2
140
141 status = adapter.status()
142 if isinstance(status, ReadError):
143 ctx.output.error(str(status))
144 return 2
145
146 muse_sync = check_muse_sync(config, status)
147 if not muse_sync.ok:
148 ctx.output.error(f"muse_sync: {muse_sync.state} — {muse_sync.message}")
149 if muse_sync.remediation:
150 ctx.output.error(f"remediation: {muse_sync.remediation}")
151 return 2
152
153 footprint_self_integrity = check_footprint_integrity(repo_root)
154 if not footprint_self_integrity.ok:
155 ctx.output.error(
156 f"footprint_self_integrity: {footprint_self_integrity.state} — "
157 f"{footprint_self_integrity.message}"
158 )
159 if footprint_self_integrity.remediation:
160 ctx.output.error(f"remediation: {footprint_self_integrity.remediation}")
161 return 2
162
163 footprint_coverage = check_footprint_coverage(
164 repo_root,
165 config,
166 kit=ctx.kit,
167 )
168 if not footprint_coverage.ok:
169 ctx.output.error(
170 f"footprint_coverage: {footprint_coverage.state} — {footprint_coverage.message}"
171 )
172 if footprint_coverage.remediation:
173 ctx.output.error(f"remediation: {footprint_coverage.remediation}")
174 return 2
175
176 injected_provider = None
177 if ctx.review_provider_factory is not None:
178 injected_provider = ctx.review_provider_factory(config.freeze_contract.reviewer.provider)
179
180 options = ReviewOptions(
181 dry_run=args.dry_run,
182 no_stamp=args.no_stamp,
183 mode=args.mode,
184 provider=args.provider,
185 model=args.model,
186 checklist=checklist,
187 kit_version=kit_version(),
188 kit_root=ctx.kit,
189 injected_provider=injected_provider,
190 )
191
192 try:
193 result = run_freeze_review(
194 artifact_path=artifact_path,
195 rel_path=rel_path,
196 config=config,
197 options=options,
198 )
199 except ValueError as exc:
200 if str(exc) == "not-utf8":
201 ctx.output.error("refused: artifact not utf-8")
202 return 4
203 raise
204
205 reviewer = resolve_reviewer_settings(config.freeze_contract, options)
206 report = build_report(
207 freeze_path=rel_path,
208 result=result,
209 reviewer=reviewer,
210 config=config.freeze_contract,
211 enabled=config.freeze_contract.enabled,
212 )
213 exit_code = resolve_exit_code(
214 result,
215 config_error=False,
216 refused=result.refused,
217 )
218 report["exit_code"] = exit_code
219
220 if ctx.output.json_mode:
221 ctx.output.emit_json(report)
222 else:
223 ctx.output.emit(render_human_report(freeze_path=rel_path, result=result))
224
225 return exit_code
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 2 days ago