feat: advance repository records and provenance

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
tegwick 2026-08-21 22:07:48 +02:00
parent 329af60753
commit 35e86d7b85
24 changed files with 1618 additions and 51 deletions

View file

@ -183,6 +183,65 @@ def main(argv: list[str] | None = None) -> int:
register_parser.add_argument("--push", action="store_true")
register_parser.add_argument("--no-commit", action="store_true")
p_intake = sub.add_parser("intake", help="Repository-owned intake records")
intake_sub = p_intake.add_subparsers(dest="record_command")
p_intake_create = intake_sub.add_parser("create", help="Create an intake")
p_intake_create.add_argument("--path", default=".")
p_intake_create.add_argument("--record-id", required=True)
p_intake_create.add_argument("--title", required=True)
p_intake_create.add_argument("--status", default="open")
p_intake_create.add_argument("--data-json", type=_json_object, default=None)
p_intake_route = intake_sub.add_parser("route", help="Route an intake")
p_intake_route.add_argument("--path", default=".")
p_intake_route.add_argument("--record-id", required=True)
p_intake_route.add_argument("--route-to", required=True)
p_intake_note = intake_sub.add_parser("note", help="Append an intake note")
p_intake_note.add_argument("--path", default=".")
p_intake_note.add_argument("--record-id", required=True)
p_intake_note.add_argument("--note", required=True)
p_intake_note.add_argument("--author", default="codex")
p_intake_close = intake_sub.add_parser("close", help="Close an intake")
p_intake_close.add_argument("--path", default=".")
p_intake_close.add_argument("--record-id", required=True)
p_intake_close.add_argument("--outcome", default=None)
p_decision = sub.add_parser("decision", help="Repository-owned decision records")
decision_sub = p_decision.add_subparsers(dest="record_command")
p_decision_create = decision_sub.add_parser("create", help="Create a decision")
p_decision_create.add_argument("--path", default=".")
p_decision_create.add_argument("--record-id", required=True)
p_decision_create.add_argument("--title", required=True)
p_decision_create.add_argument("--status", default="open")
p_decision_create.add_argument("--data-json", type=_json_object, default=None)
p_decision_update = decision_sub.add_parser("update", help="Update a decision")
p_decision_update.add_argument("--path", default=".")
p_decision_update.add_argument("--record-id", required=True)
p_decision_update.add_argument("--title", default=None)
p_decision_update.add_argument("--status", default=None)
p_decision_update.add_argument("--data-json", type=_json_object, default=None)
p_decision_resolve = decision_sub.add_parser("resolve", help="Resolve a decision")
p_decision_resolve.add_argument("--path", default=".")
p_decision_resolve.add_argument("--record-id", required=True)
p_decision_resolve.add_argument("--rationale", required=True)
p_decision_resolve.add_argument("--decided-by", required=True)
for record_parser in (
p_intake_create,
p_intake_route,
p_intake_note,
p_intake_close,
p_decision_create,
p_decision_update,
p_decision_resolve,
):
record_parser.add_argument("--reason", default="rmgr CLI")
record_parser.add_argument("--correlation-id", default=None)
record_parser.add_argument("--idempotency-key", default=None)
record_parser.add_argument("--expected-head-sha", default=None)
record_parser.add_argument("--slug", default=None)
record_parser.add_argument("--push", action="store_true")
record_parser.add_argument("--no-commit", action="store_true")
add_rapp_parser(sub)
p_conf = sub.add_parser("conform", help="Check a repository against flavor standards")
@ -205,6 +264,29 @@ def main(argv: list[str] | None = None) -> int:
p_scaf.add_argument("--force", action="store_true")
p_scaf.add_argument("--no-commit", action="store_true")
p_provenance = sub.add_parser(
"assistant-provenance",
help="Install or report coding-assistant commit provenance",
)
provenance_sub = p_provenance.add_subparsers(dest="provenance_command")
p_prov_install = provenance_sub.add_parser("install", help="Set global core.hooksPath")
p_prov_install.add_argument(
"--hooks-path",
default=str(Path(__file__).resolve().parents[2] / ".githooks"),
)
p_prov_report = provenance_sub.add_parser("report", help="Report trailers from Git history")
p_prov_report.add_argument("--path", default=".")
p_prov_report.add_argument("--rev", default="HEAD")
p_prov_report.add_argument("--max-count", type=int, default=None)
p_identifier = sub.add_parser("identifier", help="Deterministic work-record identifiers")
identifier_sub = p_identifier.add_subparsers(dest="identifier_command")
p_id_derive = identifier_sub.add_parser("derive", help="Derive one UUIDv5")
p_id_derive.add_argument("--namespace", required=True)
p_id_derive.add_argument("--record-id", required=True)
p_id_preflight = identifier_sub.add_parser("preflight", help="Scan live identifier collisions")
p_id_preflight.add_argument("--root", default=".")
args = parser.parse_args(argv)
if args.version or args.command in (None, "version"):
@ -352,6 +434,38 @@ def main(argv: list[str] | None = None) -> int:
print(json.dumps(result.to_dict(), indent=2))
return 0 if result.status == "applied" else 1
if args.command in {"intake", "decision"}:
selected = p_intake if args.command == "intake" else p_decision
if not args.record_command:
selected.print_help()
return 2
from repo_manager.commands.record import mutate_record
result = mutate_record(
Path(args.path),
args.command,
args.record_id,
operation=args.record_command,
title=getattr(args, "title", None),
status=getattr(args, "status", None),
data=getattr(args, "data_json", None),
route_to=getattr(args, "route_to", None),
note=getattr(args, "note", None),
author=getattr(args, "author", None),
outcome=getattr(args, "outcome", None),
rationale=getattr(args, "rationale", None),
decided_by=getattr(args, "decided_by", None),
correlation_id=args.correlation_id,
reason=args.reason,
commit=not args.no_commit,
push=args.push,
expected_head_sha=args.expected_head_sha,
idempotency_key=args.idempotency_key,
repo_slug=args.slug,
)
print(json.dumps(result.to_dict(), indent=2))
return 0 if result.status == "applied" else 1
if args.command == "rapp":
if args.rapp_command == "init":
result = rapp_init(
@ -438,6 +552,42 @@ def main(argv: list[str] | None = None) -> int:
print(json.dumps(result.to_dict(), indent=2))
return 0 if result.status == "applied" else 1
if args.command == "assistant-provenance":
if not args.provenance_command:
p_provenance.print_help()
return 2
from repo_manager.provenance import assistant_report, install_hook
if args.provenance_command == "install":
result = install_hook(Path(args.hooks_path))
else:
result = assistant_report(Path(args.path), rev=args.rev, max_count=args.max_count)
print(json.dumps(result, indent=2))
return 0 if result.get("ok") else 1
if args.command == "identifier":
if not args.identifier_command:
p_identifier.print_help()
return 2
from repo_manager.identifiers import derive_work_record_uuid, scan_live_identifier_collisions
if args.identifier_command == "derive":
try:
derived = derive_work_record_uuid(args.namespace, args.record_id)
except ValueError as exc:
print(json.dumps({"ok": False, "error": str(exc)}, indent=2))
return 1
result = {
"ok": True,
"namespace": args.namespace,
"record_id": args.record_id,
"uuid": str(derived),
}
else:
result = scan_live_identifier_collisions(Path(args.root))
print(json.dumps(result, indent=2))
return 0 if result.get("ok") else 1
parser.print_help()
return 0