status.py file-level

at sha256:8 · View file ↗ · Intel ↗

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