Files
govoplan/tools/devkit/govoplan_devkit/cli.py
T
zemion 2ffdb23f69
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 11m30s
feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:03:17 +02:00

213 lines
7.2 KiB
Python
Executable File

"""A compact command catalog over maintained project tools."""
from __future__ import annotations
import argparse
import importlib
import json
from pathlib import Path
import sys
from . import __version__
from .common import META_ROOT, redact, safe_output
COMMAND_MODULES = (
"context",
"doctor",
"runner",
"review",
"docs",
"issues",
"release",
"maintenance",
)
def main(argv: list[str] | None = None) -> int:
argv = sys.argv[1:] if argv is None else argv
common = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
common.add_argument(
"--workspace-root",
type=Path,
default=META_ROOT.parent,
help="Directory containing registered repositories",
)
common.add_argument(
"--project", type=Path, help="Explicit trusted portable-project JSON manifest"
)
common.add_argument(
"--state-dir",
type=Path,
help="Private evidence-state base (scoped again by workspace)",
)
common.add_argument("--format", choices=("summary", "json"), default="summary")
common.add_argument(
"--quiet",
action="store_true",
help="Suppress live progress on stderr; retain the final result",
)
common.add_argument(
"--json",
dest="format",
action="store_const",
const="json",
help="Alias for --format json",
)
parser = argparse.ArgumentParser(
prog="devkit",
description="Deterministic development workflows. Read-only previews by default for remote writes.",
parents=[common],
allow_abbrev=False,
)
parser.add_argument("--version", action="version", version=f"devkit {__version__}")
subparsers = parser.add_subparsers(dest="command", required=True)
for name in COMMAND_MODULES:
importlib.import_module("." + name, __package__).register(subparsers)
catalog = subparsers.add_parser("commands", help="Show the compact command catalog")
def commands(_):
entries = [
{
"command": "context",
"purpose": "Offline repository changes, ownership and instructions",
"effects": "read only",
},
{
"command": "doctor",
"purpose": "Local tool/dependency preflight and repair guidance",
"effects": "read only",
},
{
"command": "check",
"purpose": "Registered verification profiles with logs and source-bound receipts",
"effects": "tests/builds; --dry-run previews",
},
{
"command": "runs / latest / status / summary / logs",
"purpose": "Find runs and read progress, compact results and bounded live/final logs",
"effects": "read only",
},
{
"command": "coverage",
"purpose": "Explain declared suite coverage and exclusions for a check profile",
"effects": "read only",
},
{
"command": "resume / recover",
"purpose": "Resume verified identical work or recover an abandoned check run",
"effects": "local checks/state only",
},
{
"command": "review",
"purpose": "Module UI-review inventory and manual evidence checklist",
"effects": "local bundle only",
},
{
"command": "docs",
"purpose": "Existing documentation and translation audits",
"effects": "local checks/evidence",
},
{
"command": "issues",
"purpose": "Preview and explicitly publish deduplicated issue evidence",
"effects": "remote only with --apply",
},
{
"command": "release",
"purpose": "Existing durable release lifecycle, receipts and confirmations",
"effects": "explicit --apply and step confirmation",
},
{
"command": "git",
"purpose": "Frozen explicit-path commit and branch-push maintenance",
"effects": "explicit --apply; no bulk staging, force or tags",
},
]
return {
"commands": entries,
"summary": [
f"{item['command']}: {item['purpose']} ({item['effects']})"
for item in entries
],
}
catalog.set_defaults(handler=commands)
# Global flags work before or after the command, without copying defaults to every parser.
global_args, remaining = common.parse_known_args(argv)
args = parser.parse_args(remaining, namespace=global_args)
args.workspace_root = args.workspace_root.expanduser().resolve()
if args.project:
args.project = args.project.expanduser().absolute()
def progress(event):
if args.quiet:
return
if args.format == "json":
print(
json.dumps(safe_output(event), sort_keys=True, allow_nan=False),
file=sys.stderr,
flush=True,
)
else:
counts = event["counts"]
completed = sum(
count
for state, count in counts.items()
if state not in {"pending", "running"}
)
active = ", ".join(event["active_stages"])
print(
redact(
f"Run {event['run_id']}: {event['phase']} · {completed}/{event['total_stages']} stages · {event['elapsed_seconds']}s"
+ (f" · {active}" if active else "")
),
file=sys.stderr,
flush=True,
)
args.on_progress = progress
try:
result = args.handler(args)
if not isinstance(result, dict):
raise ValueError("Command did not return a result object")
code = int(result.pop("_exit_code", 0))
if args.format == "json":
print(
json.dumps(
safe_output(result),
sort_keys=True,
indent=2,
ensure_ascii=True,
allow_nan=False,
)
)
else:
summary = result.get("summary", [str(result.get("status", "Completed"))])
print(
"\n".join(redact(str(line)) for line in summary)
if isinstance(summary, list)
else redact(str(summary))
)
return code
except (ValueError, OSError, RuntimeError, ImportError) as exc:
message = redact(str(exc))
if args.format == "json":
print(
json.dumps(
{
"status": "error",
"error": message,
"error_type": type(exc).__name__,
}
)
)
else:
print(f"devkit: {message}", file=sys.stderr)
return 2
except KeyboardInterrupt:
print(
"devkit: interrupted; inspect the saved run before retrying",
file=sys.stderr,
)
return 130