clay-borg/tools/task-done.py

336 lines
13 KiB
Python
Raw Normal View History

CB-WP-0004 T02: make task-done — close a task on measured numbers Replaces the three hand-done steps of a task close (46 turns, $11.52 per CB-RES-0003): the heredoc flipping status in the workplan file, the hand-written hub call, and the hand-typed token counts. The third is the reason this task exists. Every update_task_status this repo produced carried estimated tokens_in/tokens_out — in a project whose central finding is that estimated token counts are worthless. task-done reads the measured figure from the transcripts, or refuses; there is no path through it that emits an estimate. cb-cost gains by_task_detail: cost, response count, model histogram and token components per task. task-done imports cb-cost rather than parsing its printed table, so the hub figure is not a copy that can drift from its source. The positive control found a real defect before the tool ran once. Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002 T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x overstatement would have been pushed to the hub as a *measured* number — the same fiction in a new form. task_label() now keys qualified subjects on the full id and leaves unqualified ones bare rather than retro-assigning them to a workplan. The pinned $93.15 benchmark is unchanged, so historical attribution was not disturbed. Fourth instance of trusted arithmetic: a number believed because a program produced it rather than a hand. Refusals, all exercised by --self-test: unknown id, typo'd id, already-done task, missing state_hub_task_id, no measured spend, and a status flip that produced no change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
#!/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:
CB-WP-0004 T02: make task-done — close a task on measured numbers Replaces the three hand-done steps of a task close (46 turns, $11.52 per CB-RES-0003): the heredoc flipping status in the workplan file, the hand-written hub call, and the hand-typed token counts. The third is the reason this task exists. Every update_task_status this repo produced carried estimated tokens_in/tokens_out — in a project whose central finding is that estimated token counts are worthless. task-done reads the measured figure from the transcripts, or refuses; there is no path through it that emits an estimate. cb-cost gains by_task_detail: cost, response count, model histogram and token components per task. task-done imports cb-cost rather than parsing its printed table, so the hub figure is not a copy that can drift from its source. The positive control found a real defect before the tool ran once. Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002 T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x overstatement would have been pushed to the hub as a *measured* number — the same fiction in a new form. task_label() now keys qualified subjects on the full id and leaves unqualified ones bare rather than retro-assigning them to a workplan. The pinned $93.15 benchmark is unchanged, so historical attribution was not disturbed. Fourth instance of trusted arithmetic: a number believed because a program produced it rather than a hand. Refusals, all exercised by --self-test: unknown id, typo'd id, already-done task, missing state_hub_task_id, no measured spend, and a status flip that produced no change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
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
CB-WP-0004 T02: make task-done — close a task on measured numbers Replaces the three hand-done steps of a task close (46 turns, $11.52 per CB-RES-0003): the heredoc flipping status in the workplan file, the hand-written hub call, and the hand-typed token counts. The third is the reason this task exists. Every update_task_status this repo produced carried estimated tokens_in/tokens_out — in a project whose central finding is that estimated token counts are worthless. task-done reads the measured figure from the transcripts, or refuses; there is no path through it that emits an estimate. cb-cost gains by_task_detail: cost, response count, model histogram and token components per task. task-done imports cb-cost rather than parsing its printed table, so the hub figure is not a copy that can drift from its source. The positive control found a real defect before the tool ran once. Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002 T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x overstatement would have been pushed to the hub as a *measured* number — the same fiction in a new form. task_label() now keys qualified subjects on the full id and leaves unqualified ones bare rather than retro-assigning them to a workplan. The pinned $93.15 benchmark is unchanged, so historical attribution was not disturbed. Fourth instance of trusted arithmetic: a number believed because a program produced it rather than a hand. Refusals, all exercised by --self-test: unknown id, typo'd id, already-done task, missing state_hub_task_id, no measured spend, and a status flip that produced no change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
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)