status.py file-level

at sha256:4 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:8 docs: queue board-identity follow-ups so they survive the session Capt… · aaronrene · Sep 5, 2026
1 """``overseer status`` command (§K4.4)."""
2
3 from __future__ import annotations
4
5 from argparse import Namespace
6 from pathlib import Path
7
8 from adapters.config import load_config
9 from adapters.errors import ConfigError, ReadError
10 from cli.context import CliContext
11 from cli.digest import sha256_hex
12 from cli.drift import compute_drift
13 from cli.footprint import resolve_footprint
14 from cli.kit_root import kit_version
15 from cli.output import CommandReport
16 from cli.paths import is_within_repo, resolve_config_path, resolve_repo_root
17 from cli.sanitize import format_config_error, sanitize_text
18 from cli.vcs_status import read_vcs_status, vcs_report
19 from cli.version_lock import LockError, lock_path, read_version_lock
20 from cli.commands.route import routing_policy_status
21 from tools.cost_awareness.format import format_cost_awareness_lines
22 from tools.cost_awareness.surface import build_cost_awareness_report, cost_awareness_payload
23 from adapters.factory import create_adapter
24 from tools.footprint_coverage import check_footprint_coverage
25 from tools.footprint_integrity import check_footprint_integrity
26 from tools.governance_freshness import check_governance_freshness
27 from tools.governance_gates import scan_governance_gates
28 from tools.governance_gates.format import format_pending_gate_lines, pending_gates_payload
29 from tools.land_closeout import check_land_closeout, land_closeout_payload
30 from tools.muse_sync import check_muse_sync
31 from tools.substrate_health import check_substrate
32 from tools.verification_evidence_gate import (
33 build_verification_evidence_gate,
34 format_verification_evidence_gate_line,
35 verification_evidence_gate_payload,
36 )
37 from tools.independent_second_reviewer import (
38 build_independent_second_reviewer_gate,
39 format_independent_second_reviewer_gate_line,
40 independent_second_reviewer_gate_payload,
41 )
42 from tools.optional_feature_tips import (
43 build_optional_feature_tips,
44 optional_feature_tips_payload,
45 )
46 from tools.workspace import build_status_report
47 from tools.workspace.board_names import status_board_name_advisory
48
49
50 GOVERNANCE_SYNC_MARKER = "last_governance_sync"
51 IDE_WORKSPACE_HINT = (
52 "ide: open the repo root (folder containing .overseer) so .cursor/rules load"
53 )
54
55
56 def _read_governance_sync_marker(repo_root: Path) -> str | None:
57 """Return timestamp line only (backward-compatible; tip fields live under governance_freshness)."""
58 marker = repo_root / ".overseer" / GOVERNANCE_SYNC_MARKER
59 if not marker.is_file():
60 return None
61 text = marker.read_text(encoding="utf-8")
62 line = text.splitlines()[0].strip() if text.strip() else ""
63 return line or None
64
65
66 def _governance_freshness_payload(report) -> dict:
67 return {
68 "state": report.state,
69 "ok": report.ok,
70 "message": report.message,
71 "remediation": report.remediation,
72 "d1": report.d1,
73 "d2": report.d2,
74 "marker_present": report.marker_present,
75 "marker_r1": report.marker_r1,
76 "actual_r1": report.actual_r1,
77 }
78
79
80 def _compute_footprint_integrity(
81 repo_root: Path,
82 lock,
83 rendered,
84 ) -> tuple[str, list[str]]:
85 """Recompute kit-only digest; report preserved-living paths separately (§K6.4)."""
86 from cli.version_lock import ORIGIN_PRESERVED, compute_lock_digest, entry_origin
87
88 prior = {entry.path: entry for entry in lock.footprint}
89 preserved_living: list[str] = []
90 kit_entries = []
91 for item in rendered:
92 entry = prior.get(item.destination)
93 origin = entry_origin(entry) if entry is not None else "kit"
94 dest = repo_root / item.destination
95 if dest.is_file():
96 content = dest.read_bytes()
97 else:
98 content = b""
99 digest_hex = sha256_hex(content)
100 if origin == ORIGIN_PRESERVED:
101 preserved_living.append(item.destination)
102 continue
103 from cli.version_lock import FootprintEntry, ORIGIN_KIT
104
105 kit_entries.append(
106 FootprintEntry(
107 path=item.destination,
108 source=item.source,
109 sha256=digest_hex,
110 origin=ORIGIN_KIT,
111 )
112 )
113 computed = compute_lock_digest(kit_entries)
114 integrity = "ok" if computed == lock.footprint_digest else "mismatch"
115 return integrity, preserved_living
116
117
118 def _lock_summary(lock) -> dict:
119 return {
120 "lock_version": lock.lock_version,
121 "kit_version": lock.kit_version,
122 "config_version": lock.config_version,
123 "footprint_digest": lock.footprint_digest,
124 "installed_at": lock.installed_at,
125 "synced_at": lock.synced_at,
126 }
127
128
129 def _exit_code_from_conditions(
130 *,
131 config_error: bool,
132 integrity: str | None,
133 drift_status: str | None,
134 use_exit_code: bool,
135 substrate_ok: bool = True,
136 muse_sync_ok: bool = True,
137 footprint_self_integrity_ok: bool = True,
138 footprint_coverage_ok: bool = True,
139 verification_evidence_gate_ok: bool = True,
140 independent_second_reviewer_gate_ok: bool = True,
141 governance_freshness_ok: bool = True,
142 land_closeout_ok: bool = True,
143 workspace_ok: bool = True,
144 ) -> int:
145 """Apply frozen precedence: 2 > 6 > 35 > 3 > 0 (§MR.7.2).
146
147 §KH2.5: ``muse_sync_ok`` folds into the 2 tier. §KH3.5: ``footprint_self_integrity_ok``
148 (declared-but-absent kit-owned files) also folds into the same 2 tier, distinct from and
149 independent of the opt-in ``--check-footprint`` content-digest ``integrity`` tier (6).
150 §GFG.6: ``governance_freshness_ok`` (D1/D2 drift or stale marker) folds into the same 2 tier.
151 §PMHF.6.1: ``land_closeout_ok`` folds into the same 2 tier; ``land_a_in_progress``
152 has ``ok=True`` so waiting for merge never false-fails status.
153 §MR.7.2: workspace relay failure is ``35`` and overrides drift ``3`` / clean ``0`` without
154 overriding config/substrate/muse_sync/footprint-self-integrity/governance-freshness (``2``)
155 or lock integrity (``6``).
156 """
157 if not use_exit_code:
158 return 0
159 if (
160 config_error
161 or not substrate_ok
162 or not muse_sync_ok
163 or not footprint_self_integrity_ok
164 or not footprint_coverage_ok
165 or not verification_evidence_gate_ok
166 or not independent_second_reviewer_gate_ok
167 or not governance_freshness_ok
168 or not land_closeout_ok
169 ):
170 return 2
171 if integrity == "mismatch":
172 return 6
173 if not workspace_ok:
174 return 35
175 if drift_status in {"behind", "ahead"}:
176 return 3
177 return 0
178
179
180 def run_status(args: Namespace, ctx: CliContext) -> int:
181 """Execute ``overseer status``."""
182 report = CommandReport()
183 repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="status")
184 overseer_dir = repo_root / ".overseer"
185
186 if not overseer_dir.is_dir():
187 payload = {
188 "initialized": False,
189 "warnings": [],
190 }
191 if ctx.output.json_mode:
192 ctx.output.emit_json(payload)
193 else:
194 ctx.output.emit("not initialized")
195 return 0
196
197 config_path = resolve_config_path(repo_root, args.config)
198 if not is_within_repo(repo_root, config_path):
199 ctx.output.error("refused: config path outside repo root")
200 return 4
201
202 config_error = False
203 lock_error = False
204
205 try:
206 config = load_config(config_path)
207 except ConfigError as exc:
208 config_error = True
209 ctx.output.error(format_config_error(exc, repo_root))
210 payload = {
211 "initialized": True,
212 "error": str(exc),
213 "warnings": report.warnings,
214 }
215 if ctx.output.json_mode:
216 ctx.output.emit_json(payload)
217 return 2
218
219 substrate = check_substrate(config, repo_root)
220 if not substrate.ok:
221 report.add_warning(f"substrate: {substrate.state} — {substrate.message}")
222 if substrate.remediation:
223 report.add_warning(f"substrate-remediation: {substrate.remediation}")
224
225 gate_scan = None
226 if config.governance_gates.remind and "status" in config.governance_gates.surfaces:
227 gate_scan = scan_governance_gates(config, repo_root)
228 if gate_scan.pending:
229 for line in format_pending_gate_lines(gate_scan):
230 report.add_warning(line)
231
232 lock_file = lock_path(repo_root)
233 lock = None
234 try:
235 lock = read_version_lock(lock_file)
236 except LockError as exc:
237 lock_error = True
238 report.add_warning(str(exc))
239
240 rendered: list = []
241 integrity: str | None = None
242 preserved_living: list[str] = []
243 if lock is not None:
244 try:
245 rendered = resolve_footprint(config, kit=ctx.kit)
246 except ConfigError as exc:
247 config_error = True
248 ctx.output.error(format_config_error(exc, repo_root))
249 return 2
250
251 if args.check_footprint:
252 integrity, preserved_living = _compute_footprint_integrity(repo_root, lock, rendered)
253 if integrity == "mismatch":
254 report.add_warning("footprint_integrity: mismatch")
255 for path in preserved_living:
256 report.add_warning(f"preserved-living: {path}")
257
258 footprint_self_integrity = check_footprint_integrity(repo_root, lock=lock)
259 if not footprint_self_integrity.ok:
260 report.add_warning(
261 f"footprint_self_integrity: {footprint_self_integrity.state} — "
262 f"{footprint_self_integrity.message}"
263 )
264 if footprint_self_integrity.remediation:
265 report.add_warning(
266 f"footprint_self_integrity-remediation: {footprint_self_integrity.remediation}"
267 )
268
269 footprint_coverage = check_footprint_coverage(
270 repo_root,
271 config,
272 lock=lock,
273 rendered=rendered if rendered else None,
274 kit=ctx.kit,
275 )
276 if not footprint_coverage.ok:
277 report.add_warning(
278 f"footprint_coverage: {footprint_coverage.state} — {footprint_coverage.message}"
279 )
280 if footprint_coverage.remediation:
281 report.add_warning(
282 f"footprint_coverage-remediation: {footprint_coverage.remediation}"
283 )
284
285 drift = compute_drift(
286 cli_version=kit_version(),
287 lock=lock,
288 rendered=rendered,
289 repo_root=repo_root,
290 )
291 if drift["status"] in {"behind", "ahead"}:
292 report.add_warning(f"drift: {drift['status']}")
293
294 vcs_result = read_vcs_status(config, repo_root, ctx.runner)
295 if isinstance(vcs_result, ReadError):
296 ctx.output.error(sanitize_text(str(vcs_result), repo_root))
297 payload = {
298 "initialized": True,
299 "substrate": _substrate_payload(substrate),
300 "vcs": {
301 "error": sanitize_text(str(vcs_result), repo_root),
302 "command": sanitize_text(vcs_result.command, repo_root),
303 },
304 "warnings": report.warnings,
305 }
306 if ctx.output.json_mode:
307 ctx.output.emit_json(payload)
308 return 2
309
310 muse_sync = check_muse_sync(config, vcs_result)
311 if not muse_sync.ok:
312 report.add_warning(f"muse_sync: {muse_sync.state} — {muse_sync.message}")
313 if muse_sync.remediation:
314 report.add_warning(f"muse_sync-remediation: {muse_sync.remediation}")
315
316 governance_freshness = check_governance_freshness(
317 config,
318 repo_root,
319 adapter=create_adapter(config, repo_root, runner=ctx.runner),
320 runner=ctx.runner,
321 )
322 if not governance_freshness.ok:
323 report.add_warning(
324 f"governance_freshness: {governance_freshness.state} — {governance_freshness.message}"
325 )
326 if governance_freshness.remediation:
327 report.add_warning(
328 f"governance_freshness-remediation: {governance_freshness.remediation}"
329 )
330
331 # §PMHF.6.1: always-on land closeout probe; never invokes gh from status.
332 land_closeout = check_land_closeout(
333 config,
334 repo_root,
335 runner=ctx.runner,
336 probe_merged_pr=False,
337 freshness=governance_freshness,
338 )
339 if not land_closeout.ok:
340 report.add_warning(f"land_closeout: {land_closeout.state} — {land_closeout.message}")
341 if land_closeout.remediation:
342 report.add_warning(f"land_closeout-remediation: {land_closeout.remediation}")
343
344 model_routing_status = routing_policy_status(config, repo_root, kit_root=ctx.kit)
345 if model_routing_status.get("enabled") and not model_routing_status.get("valid"):
346 violation = model_routing_status.get("violation") or "invalid"
347 report.add_warning(f"model_routing: invalid — {violation}")
348
349 cost_report = None
350 if config.cost_awareness.enabled and "status" in config.cost_awareness.surfaces:
351 cost_report = build_cost_awareness_report(config, repo_root, kit_root=ctx.kit)
352 if cost_report.invalid:
353 violation = cost_report.violation or "invalid"
354 report.add_warning(f"cost_awareness: invalid — {violation}")
355 elif cost_report.exit_code == 31:
356 ctx.output.error(cost_report.violation or "routing policy file missing or unreadable")
357 payload = {
358 "initialized": True,
359 "cost_awareness": cost_awareness_payload(cost_report),
360 "warnings": report.warnings,
361 }
362 if ctx.output.json_mode:
363 ctx.output.emit_json(payload)
364 return 31
365 else:
366 for line in format_cost_awareness_lines(cost_report):
367 report.add_warning(line)
368
369 handover_path = repo_root / config.repo.root_relative_docs / config.docs.handover
370 roadmap_path = repo_root / config.repo.root_relative_docs / config.docs.roadmap
371 handover_text = handover_path.read_text(encoding="utf-8") if handover_path.is_file() else None
372 roadmap_text = roadmap_path.read_text(encoding="utf-8") if roadmap_path.is_file() else None
373 verification_evidence_gate = build_verification_evidence_gate(
374 config,
375 repo_root,
376 handover_text=handover_text,
377 roadmap_text=roadmap_text,
378 )
379 ve_line = format_verification_evidence_gate_line(verification_evidence_gate)
380 if ve_line:
381 report.add_warning(ve_line)
382
383 independent_second_reviewer_gate = build_independent_second_reviewer_gate(
384 config,
385 repo_root,
386 handover_text=handover_text,
387 roadmap_text=roadmap_text,
388 )
389 isr_line = format_independent_second_reviewer_gate_line(independent_second_reviewer_gate)
390 if isr_line:
391 report.add_warning(isr_line)
392
393 optional_feature_tips = build_optional_feature_tips(config)
394
395 # §NXP.6 — warn only; must not fold into --exit-code.
396 board_name_advisory = status_board_name_advisory(config)
397 if board_name_advisory is not None:
398 report.add_warning(board_name_advisory)
399
400 payload = {
401 "initialized": True,
402 "kit_version": kit_version(),
403 "substrate": _substrate_payload(substrate),
404 "muse_sync": _muse_sync_payload(muse_sync),
405 "footprint_self_integrity": _footprint_self_integrity_payload(footprint_self_integrity),
406 "footprint_coverage": _footprint_coverage_payload(footprint_coverage),
407 "ide_workspace_hint": IDE_WORKSPACE_HINT,
408 "optional_feature_tips": optional_feature_tips_payload(optional_feature_tips),
409 "governance_freshness": _governance_freshness_payload(governance_freshness),
410 "land_closeout": land_closeout_payload(land_closeout),
411 "lock": _lock_summary(lock) if lock else None,
412 "drift": drift,
413 "footprint_integrity": integrity,
414 "preserved_living": preserved_living if args.check_footprint else [],
415 "vcs": vcs_report(vcs_result, config),
416 "last_governance_sync": _read_governance_sync_marker(repo_root),
417 "governance_gates": pending_gates_payload(gate_scan)
418 if gate_scan is not None
419 else {"enabled": False, "suppressed": False, "active_phases": [], "pending": []},
420 "model_routing": model_routing_status,
421 "warnings": report.warnings,
422 "board_name_advisory": board_name_advisory,
423 }
424 if cost_report is not None:
425 payload["cost_awareness"] = cost_awareness_payload(cost_report)
426 ve_payload = verification_evidence_gate_payload(verification_evidence_gate)
427 if ve_payload is not None:
428 payload["verification_evidence_gate"] = ve_payload
429 isr_payload = independent_second_reviewer_gate_payload(independent_second_reviewer_gate)
430 if isr_payload is not None:
431 payload["independent_second_reviewer_gate"] = isr_payload
432 if lock_error:
433 payload["lock_error"] = True
434
435 workspace_report = None
436 workspace_ok = True
437 if getattr(args, "workspace", False):
438 workspace_report = build_status_report(config, repo_root)
439 payload["workspace"] = workspace_report.to_json()
440 # S9: single-repo green must not imply workspace.ok; workspace failure
441 # contributes exit 35 only when --exit-code is set.
442 if workspace_report.configured and not workspace_report.ok:
443 workspace_ok = False
444 report.add_warning(f"workspace: {workspace_report.state}")
445
446 exit_code = _exit_code_from_conditions(
447 config_error=config_error,
448 integrity=integrity if args.check_footprint else None,
449 drift_status=drift["status"],
450 use_exit_code=args.exit_code,
451 substrate_ok=substrate.ok,
452 muse_sync_ok=muse_sync.ok,
453 footprint_self_integrity_ok=footprint_self_integrity.ok,
454 footprint_coverage_ok=footprint_coverage.ok,
455 verification_evidence_gate_ok=verification_evidence_gate.ok,
456 independent_second_reviewer_gate_ok=independent_second_reviewer_gate.ok,
457 governance_freshness_ok=governance_freshness.ok,
458 land_closeout_ok=land_closeout.ok,
459 workspace_ok=workspace_ok,
460 )
461 if lock_error and args.exit_code:
462 exit_code = 6 if exit_code in {0, 3, 35} else max(exit_code, 6)
463
464 if ctx.output.json_mode:
465 ctx.output.emit_json(payload)
466 else:
467 ctx.output.emit(f"kit_version: {payload['kit_version']}")
468 if not substrate.ok:
469 ctx.output.emit(f"substrate: {substrate.state} — {substrate.message}")
470 if substrate.remediation:
471 ctx.output.emit(f"substrate-remediation: {substrate.remediation}")
472 if not muse_sync.ok:
473 ctx.output.emit(f"muse_sync: {muse_sync.state} — {muse_sync.message}")
474 if muse_sync.remediation:
475 ctx.output.emit(f"muse_sync-remediation: {muse_sync.remediation}")
476 if not governance_freshness.ok:
477 ctx.output.emit(
478 f"governance_freshness: {governance_freshness.state} — "
479 f"{governance_freshness.message}"
480 )
481 if governance_freshness.remediation:
482 ctx.output.emit(
483 f"governance_freshness-remediation: {governance_freshness.remediation}"
484 )
485 if not land_closeout.ok:
486 ctx.output.emit(
487 f"land_closeout: {land_closeout.state} — {land_closeout.message}"
488 )
489 if land_closeout.remediation:
490 ctx.output.emit(f"land_closeout-remediation: {land_closeout.remediation}")
491 if lock:
492 ctx.output.emit(f"lock kit_version: {lock.kit_version}")
493 ctx.output.emit(f"drift: {drift['status']}")
494 if integrity:
495 ctx.output.emit(f"footprint_integrity: {integrity}")
496 if model_routing_status.get("enabled"):
497 if model_routing_status.get("valid"):
498 ctx.output.emit("model_routing: valid")
499 else:
500 violation = model_routing_status.get("violation") or "invalid"
501 ctx.output.emit(f"model_routing: invalid — {violation}")
502 if gate_scan is not None and gate_scan.pending:
503 ctx.output.emit("")
504 for line in format_pending_gate_lines(gate_scan):
505 ctx.output.emit(line)
506 if cost_report is not None and config.cost_awareness.enabled:
507 if cost_report.invalid:
508 violation = cost_report.violation or "invalid"
509 ctx.output.emit(f"cost_awareness: invalid — {violation}")
510 else:
511 for line in format_cost_awareness_lines(cost_report):
512 ctx.output.emit(line)
513 if not footprint_coverage.ok:
514 ctx.output.emit(
515 f"footprint_coverage: {footprint_coverage.state} — {footprint_coverage.message}"
516 )
517 if footprint_coverage.remediation:
518 ctx.output.emit(
519 f"footprint_coverage-remediation: {footprint_coverage.remediation}"
520 )
521 ctx.output.emit(IDE_WORKSPACE_HINT)
522 for line in optional_feature_tips:
523 ctx.output.emit(line)
524 if board_name_advisory is not None:
525 ctx.output.emit(board_name_advisory)
526 if ve_line:
527 ctx.output.emit(ve_line)
528 ctx.output.emit(f"vcs.regime: {vcs_result.regime}")
529 ctx.output.emit(f"vcs.branch: {vcs_result.branch}")
530 ctx.output.emit(f"vcs.dirty: {vcs_result.dirty}")
531 if workspace_report is not None:
532 ctx.output.emit(f"workspace.configured: {str(workspace_report.configured).lower()}")
533 ctx.output.emit(f"workspace.ok: {str(workspace_report.ok).lower()}")
534 ctx.output.emit(f"workspace.state: {workspace_report.state}")
535 if workspace_report.authoritative_handover:
536 ctx.output.emit(
537 f"authoritative_handover: {workspace_report.authoritative_handover}"
538 )
539 for member in workspace_report.members:
540 base = member.get("handover_basename") or "?"
541 ctx.output.emit(f"workspace.member {member['id']}: handover={base}")
542
543 return exit_code
544
545
546 def _substrate_payload(substrate) -> dict:
547 return {
548 "state": substrate.state,
549 "ok": substrate.ok,
550 "missing": list(substrate.missing),
551 "remediation": substrate.remediation,
552 "message": substrate.message,
553 }
554
555
556 def _muse_sync_payload(muse_sync) -> dict:
557 return {
558 "state": muse_sync.state,
559 "ok": muse_sync.ok,
560 "remediation": muse_sync.remediation,
561 "message": muse_sync.message,
562 }
563
564
565 def _footprint_self_integrity_payload(report) -> dict:
566 return {
567 "state": report.state,
568 "ok": report.ok,
569 "missing": list(report.missing),
570 "remediation": report.remediation,
571 "message": report.message,
572 }
573
574
575 def _footprint_coverage_payload(report) -> dict:
576 return {
577 "state": report.state,
578 "ok": report.ok,
579 "missing": list(report.missing),
580 "remediation": report.remediation,
581 "message": report.message,
582 }