#!/usr/bin/env python3 """Repair a stale continuation checkpoint from the authoritative task ledger. Only the generated checkpoint projection is changed. Task statuses, counts and the active ledger entry are never modified by this tool. """ from __future__ import annotations import argparse import json from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[1] STATE = ROOT / "docs/current/task-state.json" CHECKPOINT = ROOT / "docs/current/continuation-state.json" MODULE_BATCH = ROOT / "docs/current/module-batch-manifest.json" def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--quiet", action="store_true") args = parser.parse_args() state = json.loads(STATE.read_text(encoding="utf-8")) checkpoint = json.loads(CHECKPOINT.read_text(encoding="utf-8")) active = state.get("active_task") counts = state.get("counts", {}) statuses = state.get("tasks", {}) if not active or counts.get("in_progress") != 1 or statuses.get(active) != "in_progress": if not args.quiet: print("CHECKPOINT_REPAIR_NOT_APPLICABLE: ledger has no single in_progress active task") return 2 previous = checkpoint.get("activeTask") changed = False if previous != active: checkpoint["activeTask"] = active changed = True batch = None try: batch = json.loads(MODULE_BATCH.read_text(encoding="utf-8")) except (FileNotFoundError, json.JSONDecodeError): batch = None previous_batch_id = checkpoint.get("batchId") if isinstance(batch, dict) and batch.get("batchId") and previous_batch_id != batch.get("batchId"): checkpoint["batchManifest"] = "docs/current/module-batch-manifest.json" checkpoint["batchId"] = batch.get("batchId") checkpoint["batchSelection"] = batch.get("selected") changed = True if not changed: if not args.quiet: print(f"CHECKPOINT_ALREADY_ALIGNED: active={active}") return 2 checkpoint["phase"] = "CONTINUE" checkpoint["lastCheckpoint"] = "CONTINUE" checkpoint["lastTransition"] = datetime.now(timezone.utc).isoformat() checkpoint["checkpointRepair"] = { "reason": "ledger_or_batch_projection_mismatch", "previousActiveTask": previous, "repairedActiveTask": active, "previousBatchId": previous_batch_id, "repairedBatchId": checkpoint.get("batchId"), "repairedAt": datetime.now(timezone.utc).isoformat(), } temporary = CHECKPOINT.with_suffix(".repair.tmp") temporary.write_text(json.dumps(checkpoint, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") temporary.replace(CHECKPOINT) print(f"CHECKPOINT_REPAIRED: previous={previous} active={active}") return 0 if __name__ == "__main__": raise SystemExit(main())