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.
213 lines
7.2 KiB
Python
Executable File
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
|