main.py python
535 lines 20.3 KB
Raw
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 4 days ago
1 """Overseer CLI entrypoint and argument parsing (§K4.1)."""
2
3 from __future__ import annotations
4
5 import argparse
6 import sys
7 from pathlib import Path
8
9 from cli.args import extract_global_args
10
11 COMMANDS = frozenset(
12 {
13 "init",
14 "sync",
15 "status",
16 "review",
17 "check-ok",
18 "check-if-ok", # synonym → check-ok
19 "governance-sync",
20 "next",
21 "workspace",
22 "verify-step",
23 "honesty-status",
24 "ledger",
25 "route",
26 "app",
27 "hosted-dashboard",
28 "upgrade-regime",
29 "land-check",
30 "land-closeout",
31 "pr-land",
32 "handover-compact",
33 }
34 )
35 from cli.commands.app import run_app
36 from cli.commands.check_ok import run_check_ok
37 from cli.commands.governance_sync import run_governance_sync_command
38 from cli.commands.handover_compact import run_handover_compact_command
39 from cli.commands.next import run_next_command
40 from cli.commands.honesty_status import run_honesty_status_command
41 from cli.commands.hosted_dashboard import run_hosted_dashboard
42 from cli.commands.init import run_init
43 from cli.commands.land_check import run_land_check_command
44 from cli.commands.land_closeout import run_land_closeout_command
45 from cli.commands.ledger import run_ledger_command
46 from cli.commands.pr_land import run_pr_land_command
47 from cli.commands.review import run_review
48 from cli.commands.status import run_status
49 from cli.commands.sync import run_sync
50 from cli.commands.route import run_route_command
51 from cli.commands.upgrade_regime import run_upgrade_regime_command
52 from cli.commands.verify_step import run_verify_step_command
53 from cli.commands.workspace import run_workspace
54 from cli.context import CliContext
55 from cli.kit_root import kit_version
56 from cli.output import OutputContext
57
58
59 def build_parser() -> argparse.ArgumentParser:
60 """Construct the frozen argument parser."""
61 parser = argparse.ArgumentParser(prog="ok", description="Overseer Kit vendoring CLI")
62 parser.add_argument("--version", action="store_true", help="Print kit version and exit")
63 parser.add_argument("-C", "--repo", metavar="PATH", help="Repo root")
64 parser.add_argument("--config", metavar="PATH", help="Config file path")
65 parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON")
66 parser.add_argument("-q", "--quiet", action="store_true", help="Suppress non-essential stdout")
67 parser.add_argument("-v", "--verbose", action="store_true", help="Verbose diagnostics on stderr")
68 parser.add_argument("--no-color", action="store_true", help="Disable ANSI color")
69
70 subparsers = parser.add_subparsers(dest="command")
71
72 init_parser = subparsers.add_parser("init", help="First install into a repo")
73 init_parser.add_argument("--regime", choices=["muse+git-mirror", "muse-only", "git-only"])
74 init_parser.add_argument("--repo-name", metavar="NAME")
75 init_parser.add_argument("--docs-dir", metavar="PATH", default="docs")
76 init_parser.add_argument("--from-config", metavar="PATH")
77 init_parser.add_argument("--force", action="store_true")
78 init_parser.add_argument("--non-interactive", action="store_true")
79 init_parser.add_argument("--dry-run", action="store_true")
80 init_parser.add_argument(
81 "--migrate",
82 action="store_true",
83 help="Preserve existing living docs; lock origin:preserved (K6)",
84 )
85 init_parser.add_argument(
86 "--include-preserved",
87 action="store_true",
88 help="With --force: promote living docs to origin:kit (pilot-forbidden)",
89 )
90 init_parser.add_argument(
91 "--preserve-shared-assets",
92 action="store_true",
93 help=(
94 "Preserve existing differing shared assets (non-living footprint); "
95 "lock origin:preserved. With --force still does not overwrite them "
96 "unless --include-preserved"
97 ),
98 )
99
100 sync_parser = subparsers.add_parser("sync", help="Update vendored footprint")
101 sync_parser.add_argument("--dry-run", action="store_true")
102 sync_parser.add_argument("--diff", action="store_true", default=None)
103 sync_parser.add_argument("--no-diff", action="store_false", dest="diff")
104 sync_parser.add_argument("--only", action="append", metavar="GLOB")
105 sync_parser.add_argument("--force", action="store_true")
106 sync_parser.add_argument("-y", "--yes", action="store_true")
107 sync_parser.add_argument(
108 "--include-preserved",
109 action="store_true",
110 help="With --force: promote living docs to origin:kit (pilot-forbidden)",
111 )
112
113 status_parser = subparsers.add_parser("status", help="Read-only status report")
114 status_parser.add_argument("--exit-code", action="store_true")
115 status_parser.add_argument("--check-footprint", action="store_true")
116 status_parser.add_argument(
117 "--workspace",
118 action="store_true",
119 help="Attach constellation workspace report when configured (§MR.7)",
120 )
121
122 review_parser = subparsers.add_parser("review", help="Freeze-contract review")
123 review_parser.add_argument("--freeze", required=True, dest="freeze_path", metavar="PATH")
124 review_parser.add_argument("--dry-run", action="store_true")
125 review_parser.add_argument("--mode", choices=["agent", "human"])
126 review_parser.add_argument("--provider", choices=["local", "api"])
127 review_parser.add_argument("--model", metavar="LABEL")
128 review_parser.add_argument("--no-stamp", action="store_true")
129 review_parser.add_argument("--checklist", metavar="PATH")
130 review_parser.add_argument("--override-non-pass-stamp", action="store_true")
131
132 def _add_check_ok_flags(parser: argparse.ArgumentParser) -> None:
133 parser.add_argument(
134 "--path",
135 metavar="PATH",
136 help="Existing or new freeze artifact (default: docs/reviews/<date>-<topic>.md)",
137 )
138 parser.add_argument(
139 "--topic",
140 metavar="SLUG",
141 help="Topic label used when scaffolding a new side-check doc",
142 )
143 parser.add_argument(
144 "--scope",
145 metavar="TEXT",
146 help="Optional scope paragraph written into a new scaffold",
147 )
148 parser.add_argument(
149 "--scaffold-only",
150 action="store_true",
151 help="Create/reuse the artifact only; do not run freeze review",
152 )
153 parser.add_argument(
154 "--force-scaffold",
155 action="store_true",
156 help="Overwrite an existing side-check file when scaffolding",
157 )
158 parser.add_argument("--dry-run", action="store_true", help="Passed through to review --freeze")
159 parser.add_argument("--mode", choices=["agent", "human"])
160 parser.add_argument("--provider", choices=["local", "api"])
161 parser.add_argument("--model", metavar="LABEL")
162 parser.add_argument("--no-stamp", action="store_true")
163 parser.add_argument("--checklist", metavar="PATH")
164 parser.add_argument("--override-non-pass-stamp", action="store_true")
165
166 cio_parser = subparsers.add_parser(
167 "check-ok",
168 help="Check OK: ad-hoc honesty check (scaffold + review --freeze)",
169 )
170 _add_check_ok_flags(cio_parser)
171 cio_alias = subparsers.add_parser(
172 "check-if-ok",
173 help="Synonym for check-ok (deprecated name)",
174 )
175 _add_check_ok_flags(cio_alias)
176
177 gs_parser = subparsers.add_parser(
178 "governance-sync",
179 help="Governance hygiene agent (default dry-run)",
180 )
181 gs_parser.add_argument(
182 "--write",
183 action="store_true",
184 help="Apply doc patches, commit on feature branch, and push (default is dry-run)",
185 )
186 gs_parser.add_argument(
187 "--dry-run",
188 action="store_true",
189 help="Explicit dry-run (default when --write is absent)",
190 )
191 gs_parser.add_argument(
192 "--lane",
193 metavar="NAME",
194 help="Sync only this configured docs lane (requires docs.lanes in config)",
195 )
196 gs_parser.add_argument(
197 "--all-lanes",
198 action="store_true",
199 help="Sync every configured lane; skip lanes with missing doc files",
200 )
201 gs_parser.add_argument(
202 "--print-next",
203 action="store_true",
204 help="Synonym for ok next: print paste-ready fence only (no R1–R5 / patches)",
205 )
206
207 next_parser = subparsers.add_parser(
208 "next",
209 help=(
210 "Print the paste-ready NEXT fence from disk (read-only). "
211 "Not workspace check-next."
212 ),
213 )
214 next_parser.add_argument(
215 "--lane",
216 metavar="NAME",
217 help="Use this docs.lanes handover (requires docs.lanes in config)",
218 )
219
220 ws_parser = subparsers.add_parser(
221 "workspace",
222 help="Multi-repo constellation status / relay freshness (§MR.7)",
223 )
224 ws_sub = ws_parser.add_subparsers(dest="workspace_action", required=True)
225 ws_status = ws_sub.add_parser("status", help="Constellation map + relay freshness")
226 ws_status.add_argument(
227 "--strict-all",
228 action="store_true",
229 help="Treat optional member absences as failures",
230 )
231 ws_check = ws_sub.add_parser(
232 "check-next",
233 help="Fail closed on stale/ambiguous/missing relay tips (exit 35)",
234 )
235 ws_check.add_argument(
236 "--lane",
237 metavar="ID",
238 help="Constellation lane id (default: primary/product lane)",
239 )
240 ws_sub.add_parser("doctor", help="Diagnostics including board_name_violation")
241
242 lc_parser = subparsers.add_parser(
243 "land-check",
244 help="Close-ritual land-to-main check (never auto-merges)",
245 )
246 lc_parser.add_argument(
247 "--mode",
248 choices=("verify_landed", "prepare_pr"),
249 help="Override close_ritual.mode from config",
250 )
251
252 lco_parser = subparsers.add_parser(
253 "land-closeout",
254 help="Post-merge land closeout probe (§PMHF; never merges, never writes docs)",
255 )
256 lco_parser.add_argument(
257 "--probe-merged-pr",
258 action="store_true",
259 dest="probe_merged_pr",
260 default=None,
261 help="Force gh merged-PR enrichment on (default: on for git regimes)",
262 )
263 lco_parser.add_argument(
264 "--no-probe-merged-pr",
265 action="store_false",
266 dest="probe_merged_pr",
267 help="Disable gh merged-PR enrichment",
268 )
269
270 pl_parser = subparsers.add_parser(
271 "pr-land",
272 help="Authorized wait-for-green PR merge (Tier-3 delegated; requires --authorized)",
273 )
274 pl_parser.add_argument("--pr", required=True, help="PR number or URL")
275 pl_parser.add_argument(
276 "--authorized",
277 default="",
278 help='Operator authorization reason (required). Example: "Aaron: land after green"',
279 )
280 pl_parser.add_argument(
281 "--method",
282 choices=("squash", "merge", "rebase"),
283 default="squash",
284 help="Merge method (default: squash)",
285 )
286 pl_parser.add_argument("--poll-seconds", type=float, default=20.0)
287 pl_parser.add_argument("--timeout-seconds", type=float, default=1800.0)
288 pl_parser.add_argument(
289 "--allow-empty-checks",
290 action="store_true",
291 help="Treat zero reported checks as pass",
292 )
293 pl_parser.add_argument(
294 "--dry-run",
295 action="store_true",
296 help="Wait/verify only; do not merge",
297 )
298
299 vs_parser = subparsers.add_parser("verify-step", help="L1 checkpoint orchestrator (K9b)")
300 vs_parser.add_argument("--manifest", metavar="PATH", help="Active manifest path")
301 vs_parser.add_argument("--step", metavar="ID", help="Verify one step")
302 vs_parser.add_argument(
303 "--through",
304 metavar="TOKEN",
305 help="Verify through current step (only 'current' accepted)",
306 )
307 vs_parser.add_argument("--all", action="store_true", help="Verify full template order")
308 vs_parser.add_argument("--policy", metavar="PATH", help="Policy file override")
309 vs_parser.add_argument(
310 "--dry-run",
311 action="store_true",
312 help="Plan only; no script invoke or manifest writes",
313 )
314
315 hs_parser = subparsers.add_parser("honesty-status", help="L2 co-requirement check (K10)")
316 hs_parser.add_argument("--hook", metavar="HOOK")
317 hs_parser.add_argument("--artifact", metavar="PATH")
318 hs_parser.add_argument("--producer-session", metavar="ID")
319 hs_parser.add_argument("--verification-evidence", metavar="PHASE_ID")
320 hs_parser.add_argument("--frozen-spec", metavar="PATH_STRING")
321 hs_parser.add_argument("--deploy-health", metavar="PHASE_ID")
322 hs_parser.add_argument("--independent-second-review", metavar="PHASE_ID")
323 hs_parser.add_argument("--adversarial-freeze", metavar="PHASE_ID")
324 hs_parser.add_argument("--artifact-digest", metavar="DIGEST")
325
326 ledger_parser = subparsers.add_parser("ledger", help="L2 verdict ledger (K10)")
327 ledger_sub = ledger_parser.add_subparsers(dest="ledger_action", required=True)
328
329 append_parser = ledger_sub.add_parser("append", help="Append a ledger entry")
330 append_parser.add_argument("--kind", metavar="KIND", required=True)
331 append_parser.add_argument("--file", metavar="JSON_PATH")
332 append_parser.add_argument("--stdin", action="store_true")
333
334 ledger_sub.add_parser("verify", help="Verify ledger hash chain")
335
336 show_parser = ledger_sub.add_parser("show", help="Show recent ledger entries")
337 show_parser.add_argument("--last", type=int, metavar="N")
338
339 route_parser = subparsers.add_parser("route", help="Read-only model-routing resolution (§PR.6)")
340 route_parser.add_argument("--position", metavar="STR")
341 route_parser.add_argument("--phase-tier", metavar="ID", dest="phase_tier")
342 route_parser.add_argument("--gate", metavar="ID")
343 route_parser.add_argument("--validate", action="store_true")
344
345 app_parser = subparsers.add_parser("app", help="Local loopback web UI (Track Q)")
346 app_parser.add_argument("--port", type=int, default=8765, metavar="PORT")
347 app_parser.add_argument("--bind", default="127.0.0.1", metavar="ADDRESS")
348 app_parser.add_argument("--open", action="store_true", help="Open default browser after listen")
349
350 hd_parser = subparsers.add_parser(
351 "hosted-dashboard",
352 help="Read-only remote governance dashboard preview (§HGD)",
353 )
354 hd_parser.add_argument("--port", type=int, default=8766, metavar="PORT")
355 hd_parser.add_argument("--bind", default="127.0.0.1", metavar="ADDRESS")
356 hd_parser.add_argument("--config", metavar="PATH", help="Config file with hosted_dashboard block")
357 hd_parser.add_argument("--open", action="store_true", help="Open default browser after listen")
358
359 ur_parser = subparsers.add_parser(
360 "upgrade-regime",
361 help="Stage 3 ceremony: muse-only → muse+git-mirror (Track O / O3)",
362 )
363 ur_parser.add_argument(
364 "--from",
365 dest="from_regime",
366 required=True,
367 choices=["muse-only"],
368 help="Start regime (only muse-only supported in O3)",
369 )
370 ur_parser.add_argument(
371 "--to",
372 dest="to_regime",
373 required=True,
374 choices=["muse+git-mirror"],
375 help="Target regime (only muse+git-mirror supported in O3)",
376 )
377 ur_parser.add_argument(
378 "--dry-run",
379 action="store_true",
380 help="Plan C0–C5 + G1–G8 report; no writes (default when --apply absent)",
381 )
382 ur_parser.add_argument(
383 "--apply",
384 action="store_true",
385 help="Perform C2–C4 writes and C5 gates; no C7 unless --live-bridge",
386 )
387 ur_parser.add_argument(
388 "--live-bridge",
389 action="store_true",
390 help="After apply + G1–G8, run C7 via scripts/muse-bridge-deploy.sh (requires -y)",
391 )
392 ur_parser.add_argument(
393 "--force",
394 action="store_true",
395 help="Overwrite conflicting kit-owned bridge assets only (never include-preserved)",
396 )
397 ur_parser.add_argument(
398 "-y",
399 "--yes",
400 action="store_true",
401 help="C6 consent for --live-bridge after gates pass (refuse --yes alone without gates)",
402 )
403
404 hc_parser = subparsers.add_parser(
405 "handover-compact",
406 help="Archive old handover change-log bullets (§LT.6)",
407 )
408 hc_parser.add_argument(
409 "--dry-run",
410 action="store_true",
411 help="Report compaction plan without writing (default when --write absent)",
412 )
413 hc_parser.add_argument(
414 "--write",
415 action="store_true",
416 help="Apply compaction to handover and archive file",
417 )
418 hc_parser.add_argument(
419 "--keep",
420 type=int,
421 default=15,
422 metavar="N",
423 help="Number of newest dated entries to retain (default 15; minimum 5)",
424 )
425 hc_parser.add_argument(
426 "--lane",
427 metavar="NAME",
428 help="Lane name (same as ok next --lane)",
429 )
430
431 return parser
432
433
434 def main(argv: list[str] | None = None, *, ctx: CliContext | None = None) -> int:
435 """CLI main; return exit code."""
436 parser = build_parser()
437 raw_argv = list(argv) if argv is not None else sys.argv[1:]
438 global_argv, rest_argv = extract_global_args(raw_argv)
439 if rest_argv and rest_argv[0] not in COMMANDS:
440 print(f"unknown command: {rest_argv[0]}", file=sys.stderr)
441 return 1
442 try:
443 args = parser.parse_args(global_argv + rest_argv)
444 except SystemExit as exc:
445 code = exc.code
446 return 1 if code == 2 else (int(code) if isinstance(code, int) else 1)
447
448 if args.version:
449 print(kit_version())
450 return 0
451
452 if args.command is None:
453 parser.print_help()
454 return 0
455
456 runtime = ctx or CliContext.create()
457 if ctx is None:
458 runtime = CliContext.create(
459 output=OutputContext(
460 json_mode=args.json,
461 quiet=args.quiet,
462 verbose=args.verbose,
463 no_color=args.no_color,
464 ),
465 cwd=Path.cwd(),
466 )
467 else:
468 runtime.output.json_mode = args.json or runtime.output.json_mode
469 runtime.output.quiet = args.quiet or runtime.output.quiet
470 runtime.output.verbose = args.verbose or runtime.output.verbose
471 runtime.output.no_color = args.no_color or runtime.output.no_color
472
473 if args.command == "init":
474 return run_init(args, runtime)
475 if args.command == "sync":
476 if args.diff is None:
477 args.diff = sys.stdout.isatty()
478 return run_sync(args, runtime)
479 if args.command == "status":
480 return run_status(args, runtime)
481 if args.command == "review":
482 return run_review(args, runtime, raw_argv=rest_argv)
483 if args.command in {"check-ok", "check-if-ok"}:
484 return run_check_ok(args, runtime, raw_argv=rest_argv)
485 if args.command == "next":
486 return run_next_command(args, runtime)
487 if args.command == "governance-sync":
488 if getattr(args, "print_next", False):
489 if getattr(args, "write", False):
490 print(
491 "print-next mutually exclusive with --write",
492 file=sys.stderr,
493 )
494 return 2
495 if getattr(args, "all_lanes", False):
496 print(
497 "print-next mutually exclusive with --all-lanes",
498 file=sys.stderr,
499 )
500 return 2
501 return run_next_command(args, runtime)
502 return run_governance_sync_command(args, runtime)
503 if args.command == "workspace":
504 return run_workspace(args, runtime)
505 if args.command == "land-check":
506 return run_land_check_command(args, runtime)
507 if args.command == "land-closeout":
508 return run_land_closeout_command(args, runtime)
509 if args.command == "pr-land":
510 return run_pr_land_command(args, runtime)
511 if args.command == "handover-compact":
512 if not args.write and not args.dry_run:
513 args.dry_run = True
514 return run_handover_compact_command(args, runtime)
515 if args.command == "verify-step":
516 return run_verify_step_command(args, runtime)
517 if args.command == "honesty-status":
518 return run_honesty_status_command(args, runtime)
519 if args.command == "ledger":
520 return run_ledger_command(args, runtime)
521 if args.command == "route":
522 return run_route_command(args, runtime)
523 if args.command == "app":
524 return run_app(args, runtime)
525 if args.command == "hosted-dashboard":
526 return run_hosted_dashboard(args, runtime)
527 if args.command == "upgrade-regime":
528 return run_upgrade_regime_command(args, runtime)
529
530 parser.error(f"unknown command: {args.command}")
531 return 1
532
533
534 if __name__ == "__main__":
535 raise SystemExit(main())
File History 1 commit
sha256:8461d44b77376fbf06fa7c3e085d309e3010fd8d5886d63c63e69ce118811ad4 docs: record AFF-b feature-tip SHAs after AFF-b-ISR commit. Human 4 days ago