main.py python
531 lines 20.0 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day 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
131 def _add_check_ok_flags(parser: argparse.ArgumentParser) -> None:
132 parser.add_argument(
133 "--path",
134 metavar="PATH",
135 help="Existing or new freeze artifact (default: docs/reviews/<date>-<topic>.md)",
136 )
137 parser.add_argument(
138 "--topic",
139 metavar="SLUG",
140 help="Topic label used when scaffolding a new side-check doc",
141 )
142 parser.add_argument(
143 "--scope",
144 metavar="TEXT",
145 help="Optional scope paragraph written into a new scaffold",
146 )
147 parser.add_argument(
148 "--scaffold-only",
149 action="store_true",
150 help="Create/reuse the artifact only; do not run freeze review",
151 )
152 parser.add_argument(
153 "--force-scaffold",
154 action="store_true",
155 help="Overwrite an existing side-check file when scaffolding",
156 )
157 parser.add_argument("--dry-run", action="store_true", help="Passed through to review --freeze")
158 parser.add_argument("--mode", choices=["agent", "human"])
159 parser.add_argument("--provider", choices=["local", "api"])
160 parser.add_argument("--model", metavar="LABEL")
161 parser.add_argument("--no-stamp", action="store_true")
162 parser.add_argument("--checklist", metavar="PATH")
163
164 cio_parser = subparsers.add_parser(
165 "check-ok",
166 help="Check OK: ad-hoc honesty check (scaffold + review --freeze)",
167 )
168 _add_check_ok_flags(cio_parser)
169 cio_alias = subparsers.add_parser(
170 "check-if-ok",
171 help="Synonym for check-ok (deprecated name)",
172 )
173 _add_check_ok_flags(cio_alias)
174
175 gs_parser = subparsers.add_parser(
176 "governance-sync",
177 help="Governance hygiene agent (default dry-run)",
178 )
179 gs_parser.add_argument(
180 "--write",
181 action="store_true",
182 help="Apply doc patches, commit on feature branch, and push (default is dry-run)",
183 )
184 gs_parser.add_argument(
185 "--dry-run",
186 action="store_true",
187 help="Explicit dry-run (default when --write is absent)",
188 )
189 gs_parser.add_argument(
190 "--lane",
191 metavar="NAME",
192 help="Sync only this configured docs lane (requires docs.lanes in config)",
193 )
194 gs_parser.add_argument(
195 "--all-lanes",
196 action="store_true",
197 help="Sync every configured lane; skip lanes with missing doc files",
198 )
199 gs_parser.add_argument(
200 "--print-next",
201 action="store_true",
202 help="Synonym for ok next: print paste-ready fence only (no R1–R5 / patches)",
203 )
204
205 next_parser = subparsers.add_parser(
206 "next",
207 help=(
208 "Print the paste-ready NEXT fence from disk (read-only). "
209 "Not workspace check-next."
210 ),
211 )
212 next_parser.add_argument(
213 "--lane",
214 metavar="NAME",
215 help="Use this docs.lanes handover (requires docs.lanes in config)",
216 )
217
218 ws_parser = subparsers.add_parser(
219 "workspace",
220 help="Multi-repo constellation status / relay freshness (§MR.7)",
221 )
222 ws_sub = ws_parser.add_subparsers(dest="workspace_action", required=True)
223 ws_status = ws_sub.add_parser("status", help="Constellation map + relay freshness")
224 ws_status.add_argument(
225 "--strict-all",
226 action="store_true",
227 help="Treat optional member absences as failures",
228 )
229 ws_check = ws_sub.add_parser(
230 "check-next",
231 help="Fail closed on stale/ambiguous/missing relay tips (exit 35)",
232 )
233 ws_check.add_argument(
234 "--lane",
235 metavar="ID",
236 help="Constellation lane id (default: primary/product lane)",
237 )
238 ws_sub.add_parser("doctor", help="Diagnostics including board_name_violation")
239
240 lc_parser = subparsers.add_parser(
241 "land-check",
242 help="Close-ritual land-to-main check (never auto-merges)",
243 )
244 lc_parser.add_argument(
245 "--mode",
246 choices=("verify_landed", "prepare_pr"),
247 help="Override close_ritual.mode from config",
248 )
249
250 lco_parser = subparsers.add_parser(
251 "land-closeout",
252 help="Post-merge land closeout probe (§PMHF; never merges, never writes docs)",
253 )
254 lco_parser.add_argument(
255 "--probe-merged-pr",
256 action="store_true",
257 dest="probe_merged_pr",
258 default=None,
259 help="Force gh merged-PR enrichment on (default: on for git regimes)",
260 )
261 lco_parser.add_argument(
262 "--no-probe-merged-pr",
263 action="store_false",
264 dest="probe_merged_pr",
265 help="Disable gh merged-PR enrichment",
266 )
267
268 pl_parser = subparsers.add_parser(
269 "pr-land",
270 help="Authorized wait-for-green PR merge (Tier-3 delegated; requires --authorized)",
271 )
272 pl_parser.add_argument("--pr", required=True, help="PR number or URL")
273 pl_parser.add_argument(
274 "--authorized",
275 default="",
276 help='Operator authorization reason (required). Example: "Aaron: land after green"',
277 )
278 pl_parser.add_argument(
279 "--method",
280 choices=("squash", "merge", "rebase"),
281 default="squash",
282 help="Merge method (default: squash)",
283 )
284 pl_parser.add_argument("--poll-seconds", type=float, default=20.0)
285 pl_parser.add_argument("--timeout-seconds", type=float, default=1800.0)
286 pl_parser.add_argument(
287 "--allow-empty-checks",
288 action="store_true",
289 help="Treat zero reported checks as pass",
290 )
291 pl_parser.add_argument(
292 "--dry-run",
293 action="store_true",
294 help="Wait/verify only; do not merge",
295 )
296
297 vs_parser = subparsers.add_parser("verify-step", help="L1 checkpoint orchestrator (K9b)")
298 vs_parser.add_argument("--manifest", metavar="PATH", help="Active manifest path")
299 vs_parser.add_argument("--step", metavar="ID", help="Verify one step")
300 vs_parser.add_argument(
301 "--through",
302 metavar="TOKEN",
303 help="Verify through current step (only 'current' accepted)",
304 )
305 vs_parser.add_argument("--all", action="store_true", help="Verify full template order")
306 vs_parser.add_argument("--policy", metavar="PATH", help="Policy file override")
307 vs_parser.add_argument(
308 "--dry-run",
309 action="store_true",
310 help="Plan only; no script invoke or manifest writes",
311 )
312
313 hs_parser = subparsers.add_parser("honesty-status", help="L2 co-requirement check (K10)")
314 hs_parser.add_argument("--hook", metavar="HOOK")
315 hs_parser.add_argument("--artifact", metavar="PATH")
316 hs_parser.add_argument("--producer-session", metavar="ID")
317 hs_parser.add_argument("--verification-evidence", metavar="PHASE_ID")
318 hs_parser.add_argument("--frozen-spec", metavar="PATH_STRING")
319 hs_parser.add_argument("--deploy-health", metavar="PHASE_ID")
320 hs_parser.add_argument("--independent-second-review", metavar="PHASE_ID")
321
322 ledger_parser = subparsers.add_parser("ledger", help="L2 verdict ledger (K10)")
323 ledger_sub = ledger_parser.add_subparsers(dest="ledger_action", required=True)
324
325 append_parser = ledger_sub.add_parser("append", help="Append a ledger entry")
326 append_parser.add_argument("--kind", metavar="KIND", required=True)
327 append_parser.add_argument("--file", metavar="JSON_PATH")
328 append_parser.add_argument("--stdin", action="store_true")
329
330 ledger_sub.add_parser("verify", help="Verify ledger hash chain")
331
332 show_parser = ledger_sub.add_parser("show", help="Show recent ledger entries")
333 show_parser.add_argument("--last", type=int, metavar="N")
334
335 route_parser = subparsers.add_parser("route", help="Read-only model-routing resolution (§PR.6)")
336 route_parser.add_argument("--position", metavar="STR")
337 route_parser.add_argument("--phase-tier", metavar="ID", dest="phase_tier")
338 route_parser.add_argument("--gate", metavar="ID")
339 route_parser.add_argument("--validate", action="store_true")
340
341 app_parser = subparsers.add_parser("app", help="Local loopback web UI (Track Q)")
342 app_parser.add_argument("--port", type=int, default=8765, metavar="PORT")
343 app_parser.add_argument("--bind", default="127.0.0.1", metavar="ADDRESS")
344 app_parser.add_argument("--open", action="store_true", help="Open default browser after listen")
345
346 hd_parser = subparsers.add_parser(
347 "hosted-dashboard",
348 help="Read-only remote governance dashboard preview (§HGD)",
349 )
350 hd_parser.add_argument("--port", type=int, default=8766, metavar="PORT")
351 hd_parser.add_argument("--bind", default="127.0.0.1", metavar="ADDRESS")
352 hd_parser.add_argument("--config", metavar="PATH", help="Config file with hosted_dashboard block")
353 hd_parser.add_argument("--open", action="store_true", help="Open default browser after listen")
354
355 ur_parser = subparsers.add_parser(
356 "upgrade-regime",
357 help="Stage 3 ceremony: muse-only → muse+git-mirror (Track O / O3)",
358 )
359 ur_parser.add_argument(
360 "--from",
361 dest="from_regime",
362 required=True,
363 choices=["muse-only"],
364 help="Start regime (only muse-only supported in O3)",
365 )
366 ur_parser.add_argument(
367 "--to",
368 dest="to_regime",
369 required=True,
370 choices=["muse+git-mirror"],
371 help="Target regime (only muse+git-mirror supported in O3)",
372 )
373 ur_parser.add_argument(
374 "--dry-run",
375 action="store_true",
376 help="Plan C0–C5 + G1–G8 report; no writes (default when --apply absent)",
377 )
378 ur_parser.add_argument(
379 "--apply",
380 action="store_true",
381 help="Perform C2–C4 writes and C5 gates; no C7 unless --live-bridge",
382 )
383 ur_parser.add_argument(
384 "--live-bridge",
385 action="store_true",
386 help="After apply + G1–G8, run C7 via scripts/muse-bridge-deploy.sh (requires -y)",
387 )
388 ur_parser.add_argument(
389 "--force",
390 action="store_true",
391 help="Overwrite conflicting kit-owned bridge assets only (never include-preserved)",
392 )
393 ur_parser.add_argument(
394 "-y",
395 "--yes",
396 action="store_true",
397 help="C6 consent for --live-bridge after gates pass (refuse --yes alone without gates)",
398 )
399
400 hc_parser = subparsers.add_parser(
401 "handover-compact",
402 help="Archive old handover change-log bullets (§LT.6)",
403 )
404 hc_parser.add_argument(
405 "--dry-run",
406 action="store_true",
407 help="Report compaction plan without writing (default when --write absent)",
408 )
409 hc_parser.add_argument(
410 "--write",
411 action="store_true",
412 help="Apply compaction to handover and archive file",
413 )
414 hc_parser.add_argument(
415 "--keep",
416 type=int,
417 default=15,
418 metavar="N",
419 help="Number of newest dated entries to retain (default 15; minimum 5)",
420 )
421 hc_parser.add_argument(
422 "--lane",
423 metavar="NAME",
424 help="Lane name (same as ok next --lane)",
425 )
426
427 return parser
428
429
430 def main(argv: list[str] | None = None, *, ctx: CliContext | None = None) -> int:
431 """CLI main; return exit code."""
432 parser = build_parser()
433 raw_argv = list(argv) if argv is not None else sys.argv[1:]
434 global_argv, rest_argv = extract_global_args(raw_argv)
435 if rest_argv and rest_argv[0] not in COMMANDS:
436 print(f"unknown command: {rest_argv[0]}", file=sys.stderr)
437 return 1
438 try:
439 args = parser.parse_args(global_argv + rest_argv)
440 except SystemExit as exc:
441 code = exc.code
442 return 1 if code == 2 else (int(code) if isinstance(code, int) else 1)
443
444 if args.version:
445 print(kit_version())
446 return 0
447
448 if args.command is None:
449 parser.print_help()
450 return 0
451
452 runtime = ctx or CliContext.create()
453 if ctx is None:
454 runtime = CliContext.create(
455 output=OutputContext(
456 json_mode=args.json,
457 quiet=args.quiet,
458 verbose=args.verbose,
459 no_color=args.no_color,
460 ),
461 cwd=Path.cwd(),
462 )
463 else:
464 runtime.output.json_mode = args.json or runtime.output.json_mode
465 runtime.output.quiet = args.quiet or runtime.output.quiet
466 runtime.output.verbose = args.verbose or runtime.output.verbose
467 runtime.output.no_color = args.no_color or runtime.output.no_color
468
469 if args.command == "init":
470 return run_init(args, runtime)
471 if args.command == "sync":
472 if args.diff is None:
473 args.diff = sys.stdout.isatty()
474 return run_sync(args, runtime)
475 if args.command == "status":
476 return run_status(args, runtime)
477 if args.command == "review":
478 return run_review(args, runtime, raw_argv=rest_argv)
479 if args.command in {"check-ok", "check-if-ok"}:
480 return run_check_ok(args, runtime, raw_argv=rest_argv)
481 if args.command == "next":
482 return run_next_command(args, runtime)
483 if args.command == "governance-sync":
484 if getattr(args, "print_next", False):
485 if getattr(args, "write", False):
486 print(
487 "print-next mutually exclusive with --write",
488 file=sys.stderr,
489 )
490 return 2
491 if getattr(args, "all_lanes", False):
492 print(
493 "print-next mutually exclusive with --all-lanes",
494 file=sys.stderr,
495 )
496 return 2
497 return run_next_command(args, runtime)
498 return run_governance_sync_command(args, runtime)
499 if args.command == "workspace":
500 return run_workspace(args, runtime)
501 if args.command == "land-check":
502 return run_land_check_command(args, runtime)
503 if args.command == "land-closeout":
504 return run_land_closeout_command(args, runtime)
505 if args.command == "pr-land":
506 return run_pr_land_command(args, runtime)
507 if args.command == "handover-compact":
508 if not args.write and not args.dry_run:
509 args.dry_run = True
510 return run_handover_compact_command(args, runtime)
511 if args.command == "verify-step":
512 return run_verify_step_command(args, runtime)
513 if args.command == "honesty-status":
514 return run_honesty_status_command(args, runtime)
515 if args.command == "ledger":
516 return run_ledger_command(args, runtime)
517 if args.command == "route":
518 return run_route_command(args, runtime)
519 if args.command == "app":
520 return run_app(args, runtime)
521 if args.command == "hosted-dashboard":
522 return run_hosted_dashboard(args, runtime)
523 if args.command == "upgrade-regime":
524 return run_upgrade_regime_command(args, runtime)
525
526 parser.error(f"unknown command: {args.command}")
527 return 1
528
529
530 if __name__ == "__main__":
531 raise SystemExit(main())
File History 2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439 feat: K1-P1 complete — agent provenance, build-verification… Sonnet 4.6 patch 52 days ago