clay-borg/tools/task-done.py
tegwick 3e1395dd60 chore: mark T01/T02 done (measured: $2.33 + $1.68, 46 responses, opus-5)
Both hub events carry measured tokens for the first time in this repo;
--hub-only backfills T01, which was closed in the file before the tool
that closes it existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:18:34 +02:00

335 lines
13 KiB
Python

#!/usr/bin/env python3
"""Close a workplan task: flip the file, read the measured cost, tell the hub.
CB-WP-0004 T02. Replaces three things that were done by hand on every task
close, at a measured 46 turns and $11.52 per pass (CB-RES-0003 candidates
3 and 5):
1. a heredoc doing `s.replace("status: todo", "status: done")` on the
workplan file — which silently did nothing on a typo'd task id or an
already-closed task;
2. a hand-written `update_task_status` hub call;
3. **hand-typed `tokens_in` / `tokens_out`**, in a project whose central
finding is that estimated token counts are worthless. Every hub token
event this repo produced before T02 was an estimate. This tool reads
the measured figure from the transcripts via cb-cost, or refuses.
Ordering matters and is enforced, not assumed. Attribution is by commit
subject (CA-08), so the work commit naming the task must exist *before*
the close: otherwise the spend still sits in OPEN (uncommitted) and there
is nothing measured to report. The usual sequence is
<do the work>
git commit -m "CB-WP-0004 T02: ..." # subject names the task
make task-done T=CB-WP-0004-T02 # measures, flips, pushes
git commit -m "chore: mark T02 done (measured: ...)"
Usage:
python3 tools/task-done.py CB-WP-0004-T02
python3 tools/task-done.py CB-WP-0004-T02 --dry-run # measure, change nothing
python3 tools/task-done.py CB-WP-0004-T02 --no-hub # file only
python3 tools/task-done.py --self-test
"""
import glob
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.request
from repo import ROOT, enter_root
HUB = os.environ.get("CUSTODIAN_HUB", "http://127.0.0.1:8000")
AGENT = "custodian"
TASK_ID_RE = re.compile(r"^(?P<wp>CB-WP-\d+)-(?P<label>T\d\d)$")
class Fail(Exception):
"""A loud refusal. The heredocs this replaces failed silently."""
# ------------------------------------------------------------ workplan file
def find_task_block(text, task_id):
"""(start, end, body) of the ```task block declaring task_id.
Returns None when the id is absent — the caller turns that into a
non-zero exit. A string replace over the whole file would instead have
flipped whichever `status: todo` came first, which is worse than
doing nothing.
"""
for m in re.finditer(r"```task\n(.*?)```", text, re.S):
body = m.group(1)
if re.search(rf"^id:\s*{re.escape(task_id)}\s*$", body, re.M):
return m.start(1), m.end(1), body
return None
def block_status(body):
m = re.search(r"^status:\s*(\S+)\s*$", body, re.M)
return m.group(1) if m else None
def set_status(body, new):
return re.sub(r"^status:\s*\S+\s*$", f"status: {new}", body, count=1, flags=re.M)
def hub_id(body):
m = re.search(r'^state_hub_task_id:\s*"?([0-9a-f-]{36})"?\s*$', body, re.M)
return m.group(1) if m else None
def locate(task_id):
"""(path, text, span, body) for the one workplan declaring this task."""
hits = []
for path in sorted(glob.glob(os.path.join(ROOT, "workplans", "*.md"))):
text = open(path).read()
found = find_task_block(text, task_id)
if found:
hits.append((path, text, (found[0], found[1]), found[2]))
if not hits:
raise Fail(f"no workplan declares task {task_id}")
if len(hits) > 1:
raise Fail(f"{task_id} declared in {len(hits)} workplans: "
+ ", ".join(os.path.basename(h[0]) for h in hits))
return hits[0]
# ---------------------------------------------------------------- measuring
def measured(task_id):
"""Measured cost and tokens for this task, from the transcripts.
Looks up the **qualified** label (`CB-WP-0004-T01`) only. A bare `T01`
bucket collides across workplans — three of them existed when this was
written, and summing them reported $12.10 for a task that cost a
fraction of it. Falling back to the bare label would reintroduce
exactly the inflated number this tool exists to prevent, so the commit
subject must name the workplan.
Imports cb-cost rather than shelling out and parsing its printed
table: a number re-parsed out of formatted text is a copy that can
drift from its source (the DFD class).
"""
import importlib.util
spec = importlib.util.spec_from_file_location(
"cb_cost", os.path.join(ROOT, "tools", "cb-cost.py"))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
rep = mod.collect("-home-worsch-clay-borg", None)
detail = rep["by_task_detail"].get(task_id)
if not detail:
bare = task_id.rsplit("-", 1)[-1]
hint = ""
if bare in rep["by_task_detail"]:
hint = (f" A bare '{bare}' bucket exists, but it is shared with "
f"other workplans and must not be used for this task.")
raise Fail(
f"no spend attributed to {task_id}. Attribution is by commit "
f"subject (CA-08) — commit the work with '{task_id}' (or "
f"'{task_id.rsplit('-', 1)[0]} {bare}') in the subject line "
f"first, then close the task.{hint} Refusing to report an "
f"estimate: that is the habit this tool exists to end.")
return detail
# --------------------------------------------------------------------- hub
def push(task_uuid, detail, label):
body = {
"status": "done",
"tokens_in": detail["tokens_in"],
"tokens_out": detail["tokens_out"],
"model": detail["model"],
"agent": AGENT,
"token_note": (
f"measured by tools/cb-cost.py (CA-08 attribution); "
f"${detail['cost']:.2f} over {detail['responses']} responses; "
f"models {detail['models']}; tokens_in includes cache reads and "
f"writes, which the hub schema cannot separate"
),
}
req = urllib.request.Request(
f"{HUB}/tasks/{task_uuid}",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
method="PATCH",
)
try:
with urllib.request.urlopen(req, timeout=10) as r:
return r.status
except urllib.error.URLError as e:
raise Fail(f"hub PATCH failed for {label} ({task_uuid}): {e}")
# --------------------------------------------------------------- self-test
def self_test():
"""Each assertion pins a failure this tool must detect.
The controls that matter: the silent no-op that motivated the tool
(unknown id, already-done task, wrong block flipped), and the refusal
to invent token counts.
"""
results = []
def check(name, ok, detail=""):
results.append((name, ok, detail))
sample = (
"## Task: one\n\n```task\nid: CB-WP-0009-T01\nstatus: done\n"
'state_hub_task_id: "11111111-2222-3333-4444-555555555555"\n```\n\n'
"## Task: two\n\n```task\nid: CB-WP-0009-T02\nstatus: todo\n"
'state_hub_task_id: "66666666-7777-8888-9999-aaaaaaaaaaaa"\n```\n'
)
# The defect: a whole-file replace flips the first `status: todo` it
# sees, not the one asked for.
found = find_task_block(sample, "CB-WP-0009-T02")
check("finds the block for the id asked for, not the first block",
found is not None and "T02" in found[2])
check("already-done task is visible as such",
block_status(find_task_block(sample, "CB-WP-0009-T01")[2]) == "done")
check("unknown id yields nothing (caller must abort)",
find_task_block(sample, "CB-WP-0009-T99") is None)
check("typo'd id yields nothing rather than the nearest match",
find_task_block(sample, "CB-WP-0009-TO2") is None)
check("status flip touches only the target block",
set_status(found[2], "done").count("done") == 1)
check("hub uuid is extracted from the right block",
hub_id(found[2]) == "66666666-7777-8888-9999-aaaaaaaaaaaa")
check("missing hub uuid is None, not a guess",
hub_id("id: X\nstatus: todo\n") is None)
check("task id format is validated",
bool(TASK_ID_RE.match("CB-WP-0004-T02"))
and not TASK_ID_RE.match("T02")
and not TASK_ID_RE.match("CB-WP-0004"))
# The estimate refusal, exercised against a label that cannot have spend.
try:
measured("CB-WP-9999-T99")
check("refuses to report a task with no attributed spend", False,
"returned a figure for a task with no commit")
except Fail as e:
check("refuses to report a task with no attributed spend",
"Refusing to report an estimate" in str(e))
except Exception as e: # cb-cost aborted for an unrelated reason
check("refuses to report a task with no attributed spend", False, str(e))
# And that it does return real numbers for a task that has them.
try:
d = measured("CB-WP-0004-T01")
check("returns measured tokens for a committed task",
d["tokens_in"] > 0 and d["cost"] > 0,
f"${d['cost']:.2f}, {d['tokens_in']:,} in / {d['tokens_out']:,} out")
except Fail as e:
check("returns measured tokens for a committed task", False, str(e))
print("task-done self-test (positive control)")
ok = True
for name, passed, det in results:
print(f" [{'ok ' if passed else 'FAIL'}] {name}"
+ (f"{det}" if det else ""))
ok &= passed
return 0 if ok else 1
# -------------------------------------------------------------------- main
def main():
enter_root()
argv = [a for a in sys.argv[1:] if not a.startswith("--")]
flags = {a for a in sys.argv[1:] if a.startswith("--")}
if "--self-test" in flags:
return self_test()
if len(argv) != 1:
print(__doc__.strip().split("Usage:")[-1], file=sys.stderr)
return 2
task_id = argv[0]
m = TASK_ID_RE.match(task_id)
if not m:
print(f"ERROR — expected a task id like CB-WP-0004-T02, got {task_id!r}",
file=sys.stderr)
return 1
try:
path, text, (lo, hi), body = locate(task_id)
status = block_status(body)
# --hub-only exists for tasks closed in the file before this tool
# did; without it the only way to give the hub real numbers would
# be to type them, which is the defect.
if status == "done" and "--hub-only" not in flags:
raise Fail(f"{task_id} is already done in "
f"{os.path.relpath(path, ROOT)} — refusing to re-close")
uuid = hub_id(body)
if not uuid and "--no-hub" not in flags:
raise Fail(f"{task_id} has no state_hub_task_id; run fix-consistency "
f"first, or pass --no-hub")
detail = measured(task_id)
except Fail as e:
print(f"ERROR — {e}", file=sys.stderr)
return 1
rel = os.path.relpath(path, ROOT)
print(f"closing {task_id} ({rel}, status {status} -> done)")
print(f" measured ${detail['cost']:,.2f} over {detail['responses']} responses")
print(f" tokens {detail['tokens_in']:,} in / {detail['tokens_out']:,} out")
print(f" models {detail['models']}")
if "--dry-run" in flags:
print(" dry-run file unchanged, hub not called")
return 0
if "--hub-only" in flags:
try:
code = push(uuid, detail, task_id)
print(f" hub {uuid} -> done (HTTP {code}), measured tokens")
print(" file unchanged (--hub-only)")
return 0
except Fail as e:
print(f"ERROR — {e}", file=sys.stderr)
return 1
new = text[:lo] + set_status(body, "done") + text[hi:]
if new == text:
# Positive control for the exact defect being replaced.
print("ERROR — status flip produced no change; refusing to claim a close",
file=sys.stderr)
return 1
with open(path, "w") as fh:
fh.write(new)
print(f" file {rel} updated")
if "--no-hub" in flags:
print(" hub skipped (--no-hub)")
else:
try:
code = push(uuid, detail, task_id)
print(f" hub {uuid} -> done (HTTP {code}), measured tokens")
except Fail as e:
# The file is already flipped and that is recoverable by git;
# a wrong exit code is not. Report and fail.
print(f"ERROR — {e}", file=sys.stderr)
return 1
subject = (f"chore: mark {m.group('label')} done (measured: {detail['responses']} responses, "
f"${detail['cost']:,.2f}, {detail['model']})")
print(f"\nnext: git commit -m {subject!r}")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except subprocess.CalledProcessError as e:
print(f"ERROR — {e}", file=sys.stderr)
sys.exit(1)