review.py python
192 lines 6.5 KB
Raw
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 56 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.freeze_reviewer.report import build_report, render_human_report
20 from tools.substrate_health import check_substrate
21
22 DISALLOWED_FLAGS = frozenset(
23 {
24 "--write-vcs",
25 "--commit",
26 "--push",
27 "--escalate-force-pass",
28 }
29 )
30
31
32 def _validate_raw_argv(argv: list[str]) -> int | None:
33 """Reject disallowed flags with USAGE exit 1."""
34 for token in argv:
35 if token in DISALLOWED_FLAGS:
36 return 1
37 if token.startswith("--model") and "=" in token:
38 value = token.split("=", 1)[1]
39 if _looks_like_vendor_slug(value):
40 return 1
41 if token == "--model":
42 return None
43 return None
44
45
46 def _looks_like_vendor_slug(value: str) -> bool:
47 lowered = value.lower()
48 return any(marker in lowered for marker in ("gpt-", "claude-", "composer-"))
49
50
51 def _resolve_effective_checklist(args: Namespace, repo_root: Path) -> tuple[list | None, int | None]:
52 if args.checklist is None:
53 return builtin_checklist(), None
54 try:
55 checklist_path = confine_path(repo_root, args.checklist)
56 except PathEscapeError:
57 return None, 4
58 if not checklist_path.is_file():
59 return None, 4
60 try:
61 return load_checklist_file(checklist_path), None
62 except ConfigError:
63 return None, 2
64
65
66 def _resolve_artifact(args: Namespace, repo_root: Path) -> tuple[Path | None, str | None, int | None]:
67 try:
68 artifact_path = confine_path(repo_root, args.freeze_path)
69 except PathEscapeError:
70 return None, None, 4
71 if not artifact_path.is_file():
72 return None, None, 4
73 rel = repo_relative(repo_root, artifact_path)
74 return artifact_path, rel, None
75
76
77 def run_review(args: Namespace, ctx: CliContext, *, raw_argv: list[str] | None = None) -> int:
78 """Execute ``overseer review --freeze``."""
79 argv = raw_argv or []
80 disallowed = _validate_raw_argv(argv)
81 if disallowed is not None:
82 ctx.output.error("usage: invalid or disallowed flag")
83 return disallowed
84
85 repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="review")
86 overseer_dir = repo_root / ".overseer"
87 if not overseer_dir.is_dir():
88 ctx.output.error("not initialized — run overseer init first")
89 return 2
90
91 config_path = resolve_config_path(repo_root, args.config)
92 try:
93 config = load_config(config_path)
94 except ConfigError as exc:
95 ctx.output.error(format_config_error(exc, repo_root))
96 return 2
97
98 checklist, checklist_code = _resolve_effective_checklist(args, repo_root)
99 if checklist_code is not None:
100 if checklist_code == 2:
101 ctx.output.error("invalid checklist file")
102 else:
103 ctx.output.error("refused: checklist path")
104 return checklist_code
105
106 artifact_path, rel_path, artifact_code = _resolve_artifact(args, repo_root)
107 if artifact_code is not None or artifact_path is None or rel_path is None:
108 ctx.output.error("refused: artifact path")
109 return artifact_code or 4
110
111 # Validate model label when agent mode effective
112 effective_mode = args.mode or config.freeze_contract.reviewer.mode
113 effective_model = args.model or config.freeze_contract.reviewer.model
114 if effective_mode != "human":
115 if args.model and _looks_like_vendor_slug(args.model):
116 ctx.output.error("reviewer.model must be a label, not a vendor slug")
117 return 1
118 try:
119 validate_reviewer_model(effective_model, ctx.kit)
120 except ConfigError as exc:
121 ctx.output.error(format_config_error(exc, repo_root))
122 return 2
123 if args.provider and args.provider not in {"local", "api"}:
124 ctx.output.error("invalid --provider value")
125 return 1
126 if args.mode and args.mode not in {"agent", "human"}:
127 ctx.output.error("invalid --mode value")
128 return 1
129
130 adapter = create_adapter(config, repo_root, runner=ctx.runner)
131 substrate = check_substrate(config, repo_root)
132 if not substrate.ok:
133 ctx.output.error(f"substrate: {substrate.state} — {substrate.message}")
134 if substrate.remediation:
135 ctx.output.error(f"remediation: {substrate.remediation}")
136 return 2
137
138 status = adapter.status()
139 if isinstance(status, ReadError):
140 ctx.output.error(str(status))
141 return 2
142
143 injected_provider = None
144 if ctx.review_provider_factory is not None:
145 injected_provider = ctx.review_provider_factory(config.freeze_contract.reviewer.provider)
146
147 options = ReviewOptions(
148 dry_run=args.dry_run,
149 no_stamp=args.no_stamp,
150 mode=args.mode,
151 provider=args.provider,
152 model=args.model,
153 checklist=checklist,
154 kit_version=kit_version(),
155 kit_root=ctx.kit,
156 injected_provider=injected_provider,
157 )
158
159 try:
160 result = run_freeze_review(
161 artifact_path=artifact_path,
162 rel_path=rel_path,
163 config=config,
164 options=options,
165 )
166 except ValueError as exc:
167 if str(exc) == "not-utf8":
168 ctx.output.error("refused: artifact not utf-8")
169 return 4
170 raise
171
172 reviewer = resolve_reviewer_settings(config.freeze_contract, options)
173 report = build_report(
174 freeze_path=rel_path,
175 result=result,
176 reviewer=reviewer,
177 config=config.freeze_contract,
178 enabled=config.freeze_contract.enabled,
179 )
180 exit_code = resolve_exit_code(
181 result,
182 config_error=False,
183 refused=result.refused,
184 )
185 report["exit_code"] = exit_code
186
187 if ctx.output.json_mode:
188 ctx.output.emit_json(report)
189 else:
190 ctx.output.emit(render_human_report(freeze_path=rel_path, result=result))
191
192 return exit_code
File History 1 commit
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 56 days ago