feat(RMGR-WP-0002): dual-run writeback, flags, meter, SH facade

Add dual-run flags/meter, harden task-status (idempotency, UUID, head,
push), State Hub adapter for PATCH /tasks and C-15/reconcile proxy, pilot
evidence, and finish RMGR-WP-0002.
This commit is contained in:
tegwick 2026-08-09 23:19:56 +02:00
parent bb1d030257
commit 310b43079d
14 changed files with 679 additions and 50 deletions

View file

@ -17,6 +17,7 @@ def main(argv: list[str] | None = None) -> int:
sub = parser.add_subparsers(dest="command")
sub.add_parser("version", help="Print version")
sub.add_parser("dual-run-status", help="Show dual-run flags and mutation meter")
p_obs = sub.add_parser("observe", help="Observe repository + print snapshot JSON")
p_obs.add_argument("--path", default=".", help="Repository checkout path")
@ -25,12 +26,6 @@ def main(argv: list[str] | None = None) -> int:
p_rec = sub.add_parser("reconcile", help="Rebuild local work-record index from files")
p_rec.add_argument("--path", default=".", help="Repository checkout path")
p_rec.add_argument("--slug", default=None)
p_rec.add_argument(
"--write-index",
action="store_true",
default=True,
help="Write .repo-manager/index.json (default true)",
)
p_rec.add_argument("--no-write-index", action="store_true", help="Do not write index file")
p_cmd = sub.add_parser(
@ -38,7 +33,11 @@ def main(argv: list[str] | None = None) -> int:
help="Command repo.work.update_task_status (file + git commit)",
)
p_cmd.add_argument("--path", default=".", help="Repository checkout path")
p_cmd.add_argument("--task-id", required=True, help="Canonical task id e.g. RMGR-WP-0001-T05")
p_cmd.add_argument(
"--task-id",
required=True,
help="Canonical task id or State Hub task UUID",
)
p_cmd.add_argument(
"--status",
required=True,
@ -46,6 +45,10 @@ def main(argv: list[str] | None = None) -> int:
)
p_cmd.add_argument("--reason", default="rmgr CLI")
p_cmd.add_argument("--correlation-id", default=None)
p_cmd.add_argument("--idempotency-key", default=None)
p_cmd.add_argument("--expected-head-sha", default=None)
p_cmd.add_argument("--slug", default=None)
p_cmd.add_argument("--push", action="store_true", help="git push after commit (push-seal)")
p_cmd.add_argument(
"--no-commit",
action="store_true",
@ -58,7 +61,13 @@ def main(argv: list[str] | None = None) -> int:
from repo_manager import __version__
print(__version__)
return 0 if args.command or args.version else 0
return 0
if args.command == "dual-run-status":
from repo_manager.dual_run import flags_status
print(json.dumps(flags_status(), indent=2))
return 0
if args.command == "observe":
from repo_manager.observe import observe_repository
@ -68,6 +77,7 @@ def main(argv: list[str] | None = None) -> int:
return 0
if args.command == "reconcile":
from repo_manager.dual_run import record_mutation
from repo_manager.index_store import append_event, save_index
from repo_manager.observe import observe_repository
@ -79,8 +89,15 @@ def main(argv: list[str] | None = None) -> int:
"type": "repo.reconciled",
"workplan_count": snap["index"]["workplan_count"],
"task_count": snap["index"]["task_count"],
"source": "repo-manager",
},
)
record_mutation(
source="repo-manager",
kind="reconcile",
repo_slug=snap.get("slug"),
detail=snap["index"],
)
if not args.no_write_index:
path = save_index(index)
print(json.dumps({"ok": True, "index_path": str(path), "snapshot": snap}, indent=2))
@ -98,6 +115,10 @@ def main(argv: list[str] | None = None) -> int:
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