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.
This commit is contained in:
Executable
+212
@@ -0,0 +1,212 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user