aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/wflib/ledgers.py
diff options
context:
space:
mode:
Diffstat (limited to 'wflib/ledgers.py')
-rw-r--r--wflib/ledgers.py35
1 files changed, 35 insertions, 0 deletions
diff --git a/wflib/ledgers.py b/wflib/ledgers.py
new file mode 100644
index 0000000..e22caa5
--- /dev/null
+++ b/wflib/ledgers.py
@@ -0,0 +1,35 @@
+"""In-flight plan ledgers (superpowers executing-plans / subagent-driven-development):
+<ledgers>/<plan>/progress.md, first line `# SDD ledger — plan: <path>`."""
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+TASK_RE = re.compile(r"^### Task (\S+?):", re.M)
+DONE_RE = re.compile(r"^Task (\S+?): complete", re.M)
+
+
+def summary(plan_text: str, ledger_text: str) -> str:
+ """'done 1, 2; resume at Task 3 (…)' / 'done 1, 2, 3; all tasks done; Final review: …'."""
+ planned = TASK_RE.findall(plan_text)
+ done = DONE_RE.findall(ledger_text)
+ left = [t for t in planned if t not in done]
+ finals = [l for l in ledger_text.split("\n") if l.startswith("Final review")]
+ head = f"done {', '.join(done) if done else 'none'}"
+ if left:
+ return f"{head}; resume at Task {left[0]} (task-start PLAN {left[0]})"
+ return f"{head}; all tasks done; {finals[-1] if finals else 'final review not dispatched yet'}"
+
+
+def in_flight(root: Path, ledgers: Path) -> list[str]:
+ out = []
+ for ledger in sorted(ledgers.glob("*/progress.md")):
+ text = ledger.read_text(encoding="utf-8", errors="replace")
+ first = text.split("\n", 1)[0]
+ plan = first.split("plan:", 1)[1].strip() if "plan:" in first else ""
+ plan_path = root / plan
+ plan_text = plan_path.read_text(encoding="utf-8", errors="replace") if plan and plan_path.is_file() else ""
+ rulings = sum(1 for l in text.split("\n") if "Ruling:" in l)
+ out.append(f"- {plan or ledger.parent.name}: {summary(plan_text, text)} "
+ f"({rulings} rulings; ledger {ledger.relative_to(root)})")
+ return out