the-custodian/scripts/requalify_tasks.py

80 lines
2.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Apply task-block requalifications across repo workplans from a JSON plan.
Deterministic replacement for agent sessions that re-read wait tasks after a
decision resolves. Plan format (list of objects):
{"id": "RPF-WP-0044-T03", "set": {"status": "todo"},
"drop": ["needs_human", "decision_id"], "note": "D2 resolved 2026-10-11"}
`set` replaces or adds keys (values are written verbatim). `drop` removes keys.
`blocking_reason` is dropped automatically when the new status is not `wait`.
`note` is appended after the task block as a dated line. Use --dry-run first.
"""
import argparse, glob, json, os, re, sys
BLOCK = re.compile(r"```task\n(.*?)```", re.S)
def edit_block(body, change):
lines = body.rstrip("\n").split("\n")
drop = set(change.get("drop", []))
new_status = change.get("set", {}).get("status")
if new_status and new_status != "wait":
drop |= {"blocking_reason", "needs_human", "blocked_on"}
out, seen = [], set()
for ln in lines:
key = ln.split(":", 1)[0].strip()
if key in drop:
continue
if key in change.get("set", {}):
ln = f"{key}: {change['set'][key]}"
seen.add(key)
out.append(ln)
# insert new keys before state_hub_task_id so the writeback field stays last
extra = [f"{k}: {v}" for k, v in change.get("set", {}).items() if k not in seen]
idx = next((i for i, l in enumerate(out) if l.startswith("state_hub_task_id")), len(out))
out[idx:idx] = extra
return "\n".join(out) + "\n"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("plan")
ap.add_argument("--root", default=os.path.expanduser("~"))
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
plan = {c["id"]: c for c in json.load(open(a.plan))}
touched, found = set(), set()
for f in glob.glob(os.path.join(a.root, "*/workplans/*.md")):
text = open(f, encoding="utf-8").read()
def repl(m):
body = m.group(1)
mid = re.search(r"^id:\s*(\S+)", body, re.M)
if not mid or mid.group(1) not in plan:
return m.group(0)
c = plan[mid.group(1)]
found.add(c["id"])
new = "```task\n" + edit_block(body, c) + "```"
if c.get("note"):
new += f"\n\n{c['note']}"
return new
new_text = BLOCK.sub(repl, text)
if new_text != text:
touched.add(f)
if not a.dry_run:
open(f, "w", encoding="utf-8").write(new_text)
for f in sorted(touched):
print(("would edit " if a.dry_run else "edited ") + f)
missing = set(plan) - found
if missing:
print("NOT FOUND:", ", ".join(sorted(missing)), file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())