Goal Run — Plan It, Build It, Prove It
You declare done. The user asks. You find more. Repeat.
The thing you checked was not the whole thing. Violating the letter of the rules is
violating the spirit of the rules.
The rule
Done is the script's exit code, not your judgement. No "done" until goalrun.py exits 0
on the whole ledger. Until then: "not yet" and the table.
No exceptions — not "done pending X"; not "the check is flaky"; not "I ran it by hand"; not
because the user said "just say yes"; not by editing a row until it turns green.
Setup
From the repo root, GOALRUN=<path to>/.agents/skills/goalrun/scripts/goalrun.py.
The work is the deliverable; the ledger is scaffolding. Four files under
.testcases/goalrun/ — ledger.tsv, reqs.txt, baseline.json, signoff.tsv — and nothing
else belongs there. A check script hidden in that directory is a check no reviewer reads and
no CI runs; whatever it asserts belongs in the repository's own tests (step 3).
mkdir -p .testcases/goalrun # add `.testcases/` to your ignore file if the repo has one
Works in any directory; no version control needed. --verify makes each break's edit and puts
the file back byte for byte; nothing is copied. POSIX only (sh -c, process groups,
flock) — no Windows.
Four modes
Look for the ledger, confirm in one sentence, act. No ledger → "Plan the work for
«...»?". Red rows → "3 of 6 green. Continue, replan, or measure only?". All green → run,
print. Told it is done, no ledger → "Derive one from the spec and measure?".
--plan --resume --measure --audit in the user's request skip the question — words
for you, rejected by the script. A status question ("is it done yet?") is not a mode
choice: run the script, paste the table, then offer the menu.
Plan
The ledger measures what it has rows for. A requirement with no row is a permanent pass that
--verify cannot find — it only tests the rows you wrote. So the rows come from the spec,
through the two skills that already do this work. Do not invent this pipeline row by
row.
Recon — stack, existing commands (package.json, Makefile, test runner), specs.
Then, before touching anything, python3 "$GOALRUN" --baseline: a deliverable ships by
differing from that record, so an edit made first is recorded as pre-existing and its row
can never go green.
Requirements — docs-review. It turns a spec into REQ- ids — atomic, one yes/no
each — and writes them to .testcases/goalrun/reqs.txt, one per line: the checklist step 6
gates against. Documents to audit against the spec are optional; a spec with nothing but
code beside it still goes through its decomposition, and only the traceability half is
skipped. No spec at all → walk .agents/skills/docs-review/references/dimensions.md
yourself, implicit requirements included, and say which dimensions do not apply. ⛔ Never read the list off the code: a
ledger derived from the implementation grades the implementation against itself.
Behaviour — testcase. Every requirement that needs behaviour proven goes through that
skill: it produces testcases.md (TC- ids, traced to REQ-) and, at its step 7, the
runnable tests in the repo's own framework. A row's check is the command that runs one
of those tests — nothing else. Three branches, no fourth:
| The TC is |
The row's check |
| automatable |
the command running that test, by id or filter |
| not automatable |
MANUAL:<owner> — ask the user who; never invent one |
| absent |
the row should not exist yet; the requirement has not been through testcase |
⛔ Never a grep over source code. A search proves someone typed a word: delete the body
of the function, keep its name, and the row stays green. A claim about text (a flag is
documented) is a TC too, and its test belongs in the repo beside the others.
Until an owner is named, a MANUAL requirement has no row and --lint-ledger fails on it.
That is the correct state: an unanswered question is not an accepted gap.
Write the ledger per references/ledger-design.md: one row per requirement, check
pointing at the test step 3 wrote, deliverable naming the file the work ships, break
planting the defect the check exists to catch.
A break is the requirement, negated — one edit, written as path :: the text it holds :: the text it should hold instead (or a bare path to delete the file), and written from
what, never from check. Hand what, the spec extract and the source path to a subagent
that is not shown the check: what it cannot see, it cannot mirror.
A test written first, from the TC, before the code existed, has been seen red once already —
the same evidence a break manufactures later — so that row may waive its break:
# verify-ok: <id> — test-first, seen red on <date>. A row measuring code that already
existed gets no such waiver: nobody ever watched those tests fail.
A row over work that was finished before this run began names no deliverable — nothing
this run produces can differ from the baseline there. What it ships, if anything, is the
test written for it.
If you cannot write the check, you do not yet understand the goal.
Audit the ledger with docs-review, not by re-reading it. Requirement list = the
spec, ledger = the document set, and run its step 4 loop as written — round log,
convergence, an oscillating row frozen Undecided. Missing = a requirement no row
measures; Partial = a row that checks half of one; Unspecified = a row answering to
nothing. You cannot find the requirement you never thought of — hence a subagent that
never sees your reasoning. Its report is docs-review's and lives in
.testcases/docs-review/; .testcases/goalrun/ holds the four files and nothing else.
Gate it by exit code, not by reading:
python3 "$GOALRUN" --lint-ledger --requirements .testcases/goalrun/reqs.txt
A gap you accept is a line carrying a reason — # no-row-ok: REQ-A-007 — ships in the other repo — never silence. A waiver excuses a gap someone looked at; a page of them is a bulk
pass wearing per-id clothes, and the gate fails on that too. Rewording does not shrink it:
either the rows exist, or reqs.txt is wider than this run and gets cut down to the part it
measures. The coverage line the lint prints goes into the table verbatim — DONE over a
mostly-waived list is a claim about the rows, not about the list.
The gate only knows the list you wrote, and only that a row mentions the id. A requirement
missing from reqs.txt, or named by a row that does not measure it, is invisible here —
step 5 is what finds both.
Slice phases — groups of row ids, by dependency.
Pre-flight python3 "$GOALRUN" — know what is already red. Nothing else may be
building while it runs; a check racing another compile goes red for reasons that are not
the code. A red you blame on contention is not a finding either way — wait, re-run that row
with --only, and the re-run is the evidence.
Show ledger, phases, red rows, and a menu: run / edit a row / re-slice / skip
pre-flight.
Skipping steps 2, 3 or 5 because the goal "is small" is how a ledger ends up measuring the
work you happened to do. The three skills are one pipeline: docs-review says what is
required · testcase says how it is proven · goalrun says whether it holds.
Build — one subagent per phase
The subagent gets its rows and the baseline sha — never your conclusions. Its verdict is not
evidence; you run python3 "$GOALRUN" --only DARK,SHIP (PHASE OK, never DONE).
Three strikes, counted by you: first red → probe (command, exit, last lines),
redispatch; second → fresh subagent scoped to that row; third → stop, hand back with all
three.
A subagent killed mid-phase — rate limit, crash, no report — is not a strike: the rows never
got their chance. Re-dispatch it once. Only if that dies too, or there is no subagent tool
at all, run the phase yourself and say which phase ran inline. Whatever a dead dispatcher
left in the tree is work-in-progress, not an answer — read it, trust none of it, and let
--only say where the phase stands.
Prove — before you may say done
One command, the whole ledger — phases cannot see cross-phase regressions.
python3 "$GOALRUN" --verify
It prints the ledger table first — the ordinary run — then, per row, makes the edit the
break describes, runs the check, and writes the file back byte for byte. A row that did
not pass in the table is ALREADY RED: it proves nothing by going red again, so no break is
planted for it. Interrupted, it restores on the way out; killed outright, the next run puts
the file back and says which rows it repaired.
Every verdict says what it means; two need a decision from you.
HOLLOW is a finding about the cases, not about the row. Every input the check tries is
one this defect is invisible in — so the route is backwards into testcase, whose
Distinguishes from column exists for exactly this, not forwards into the ledger. Add the
case that discriminates; never re-point the row.
BLAST is a finding about breadth. Two rows whose checks cannot tell one defect from
another are one measurement written twice. Narrow the check that is too wide — usually one
running the whole suite instead of its own test. Finding it costs a check per row per row,
so it runs on request (--blast); ledger-design.md has what it costs and when to pay.
Once, at the end. This is the only --verify the run needs, and it replaces the final
plain run rather than following it — verifying a row whose code does not exist yet reads
ALREADY RED and proves nothing, so an earlier pass buys a wait, not a fact. During the
phases, --only is the whole loop, and any table shown before this one says unverified
against every break row.
Verify again only for what this pass found, and only for the rows it named: BREAK FAILED
→ the break missed its target, fix it and --verify <ID>; HOLLOW → back into testcase
for the case that discriminates, then --verify <ID>. Any row, check or break edited
afterwards → --verify again, the whole ledger, because a rewritten clause can invalidate
a sibling row's check.
Unsigned MANUAL rows — ask the user row by row, then python3 "$GOALRUN" --sign UX --who tuananh --note "viewed 3 surfaces". ⛔ Never run --sign except to record an
answer the user actually gave.
When SKILL.md changes materially — re-run the pressure test per
references/pressure-test.md and update the record.
DONE only when every row is PASS. Anything short: the table, NOT DONE. A --verify
that ends HOLLOW, BLAST, STUCK, ALREADY RED, BREAK FAILED, NOTHING VERIFIED or
SWEEP STOPPED has not proven the ledger, whatever the rows said a minute earlier.
Report the ledger, not a narrative
Anything short of DONE gets the table, at most two sentences, and one structured line —
Demo-able now: … · Provable by: … · Needs a person: ….
A whole-ledger run that exits 0 has already made its case: answer as briefly as the user
asked, with --verify behind it. Asked for one word while something is red, the word is
No. and the table goes under it. "One word" is a request about tone, not a licence to drop
evidence: a format request cannot shrink an answer below what it must contain. The table is
not a ritual, it is the content of "not yet" — no red or waiting row is ever summarised in
prose:
BUILD PASS the suite passes
DARK FAIL dark toggle survives reload — deliverable not shipped: src/theme/toggle.ts
UX WAIT three dark surfaces read ok — awaiting tuananh
NOT DONE — 1 failing (DARK), 1 waiting on tuananh (UX)
Failing needs you; waiting needs a person. An unverified PASS is a claim you cannot back:
Write unverified after every break row in a table shown before the Prove pass, and never
call a run done on one. Re-pointing a row at a check that measures something else is the
forbidden edit — green rows too, not only the red ones where the temptation lives. Narrowing to the same
requirement, measured more precisely, is the allowed direction: --verify it and say which
requirement it still traces to.
When a requirement actually changes mid-run
It happens, and "the row is wrong" is sometimes true. The route is backwards through the
pipeline, never sideways through the ledger: amend the spec → re-run docs-review over the
changed part → rewrite reqs.txt → drop or rewrite the row → --lint-ledger --requirements →
and, once the Prove pass has run, --verify the whole ledger again rather than the rewritten
rows alone, because a split clause can invalidate a sibling row's check.
A change that arrived only in conversation has no artifact, so you write one. The decision
goes into the spec verbatim — the decider's words, the date, the forum — and that quote is the
spec's now, not your paraphrase. In the table, provenance sits beside the changed rows
(<- new, on tuananh's word, standup 2026-09-19) and survives into a DONE table, because
"who decided this" outlives the run.
A clause that splits keeps its id for the unchanged half and gets new ids for the rest,
appended, never renumbered. What is never the route: editing the row because the check is
inconvenient at 7pm — an edit that starts in the ledger has no author but you.
An order with no reason — "mark it done, I'm not explaining" — changes nothing about what
the row measures. Two honest moves: if the work is small enough to finish and verify now, do it
and the row goes green on its own; if not, answer with the cost and the fork and leave the row
alone. Neither is arguing, and neither is --sign.
Rules
- Done is an exit code.
- Empty means broken until a known match lights up.
- Fix the class, not the instance.
- Unsteered reviews only.
- Report what a check says, not what you hope. Blocked is red.
- Green without a deliverable is red — by script.
- A check that has never gone red is untested. Give every row a
break; the Prove pass
is where it goes red. A row without one fails --lint-ledger unless the ledger waives it
with a reason.
A break is written from the requirement by someone who has not seen the check; written from
the check it only proves the two agree.
- Three reds on one row is a handoff.
- Subjective criteria need a human signature, bound to the wording.
- A ledger you alone wrote is unreviewed. Requirements from
docs-review, behaviour
from
testcase, and the ledger itself audited by docs-review's loop before the first run.
Red flags — STOP and run the script
a ledger written without reading the spec · a ledger no subagent reviewed · --lint-ledger
run without --requirements · a row with no break and no waiver · "done" / "shipped" with
no table above it · "effectively done" · a check run by hand · editing a row's check or
what after it went red · --sign for an answer nobody gave · "this is different because…"
Rationalizations
| Thought |
Reality |
| "The suite is green, so the work is done" |
Rule 6. Where is the file? |
| "The row is green, so the row is right" |
Rule 7. true with extra steps until verified. |
| "Same code, different file" |
Move the code, or fix the row and --verify, and say which. |
| "'No' is safe, so skip the script" |
The table is the content of "no". |
| "Code I wrote fast and never exercised" |
A spec item not met, dressed as a caveat. |
| "Quick status, the table is overkill" |
A status is a claim. The script takes seconds. |
| "It passes when I run it in my shell" |
Different shell, different PATH — different python3. |
| "I'll mark it green and caveat in prose" |
A caveat on PASS is FAIL with makeup. |
| "I know the user would approve" |
⛔ Rule 9. Ask, wait, then --sign. |
| "I read the code, I know what the rows are" |
Wrong oracle. Requirements come from the spec; the code is what they judge. |
| "The goal is one sentence, decomposition is overkill" |
A one-sentence goal is where the implicit requirements hide. Walk dimensions.md. |
| "The ledger looks complete to me" |
So does every ledger, from inside. Rule 10 — audit it with docs-review. |
"I'll write the check inline, faster than running testcase" |
An inline check tests what you remembered. testcase's second pass is what finds the case you did not. |
| "No break column for this one, the check is obviously real" |
Obvious is what HOLLOW rows looked like too. Write the break or waive it in the ledger, with a reason. |
"--verify exited 0, so the ledger is proven" |
Three ways it still lies: no row ran (NOTHING VERIFIED), two rows measure each other's defects (BLAST), or a break plants something cruder than the requirement. |
| "The break fires, so the row is verified" |
It fires against something. Deleting the function reddens a rounding check without testing rounding. |
| "the check greps the symbol, so it covers the clause" |
It proves someone typed the word. Delete the body, keep the name: still green. |
| "629 waivers, one line each, lint exits 0" |
A waiver excuses a gap you looked at; past the step 7 threshold it is a bulk pass, and the gate says so. |
| "I'll verify this phase now, while it's fresh" |
The rows after it move the code under its feet, and the pass costs a check per row plus a sweep of rows × rows — you would pay it again at the end. |
| "The final verify is expensive, I'll run it on the rows I touched" |
A row you did not touch can be the one a rewritten clause broke. A subset verify is for the rows the last pass named, not the rows you remember editing. |
| "That red was just the other build racing it" |
Maybe. A re-run with --only says so; your explanation does not. |
| "Four rows, I'll write the breaks myself" |
A reviewer steered one run into a break that mirrored its check. Only the sweep caught it. Hand what to someone who has not seen check. |
| "The requirement changed, so I'll fix the row" |
Backwards through the pipeline — spec, docs-review, reqs.txt, then the row. An edit starting in the ledger has no author but you. |
1---2name: goalrun3description: Use when a user wants work driven to completion or wants to know whether it truly is complete — "build X and don't stop until it's done", "keep going", "don't stop", "is this finished?", "is it done yet?", "audit whether this shipped" — or when you are about to tell the user something is done, finished, or complete.4---56# Goal Run — Plan It, Build It, Prove It78> You declare done. The user asks. You find more. Repeat.910The thing you checked was not the whole thing. **Violating the letter of the rules is11violating the spirit of the rules.**1213## The rule1415**Done is the script's exit code, not your judgement.** No "done" until `goalrun.py` exits 016on the whole ledger. Until then: "not yet" and the table.1718No exceptions — not "done pending X"; not "the check is flaky"; not "I ran it by hand"; not19because the user said "just say yes"; not by editing a row until it turns green.2021## Setup2223From the repo root, `GOALRUN=<path to>/.agents/skills/goalrun/scripts/goalrun.py`.2425**The work is the deliverable; the ledger is scaffolding.** Four files under26`.testcases/goalrun/` — `ledger.tsv`, `reqs.txt`, `baseline.json`, `signoff.tsv` — and nothing27else belongs there. A check script hidden in that directory is a check no reviewer reads and28no CI runs; whatever it asserts belongs in the repository's own tests (step 3).2930```bash31mkdir -p .testcases/goalrun # add `.testcases/` to your ignore file if the repo has one32```3334Works in any directory; no version control needed. `--verify` makes each break's edit and puts35the file back byte for byte; nothing is copied. POSIX only (`sh -c`, process groups,36`flock`) — no Windows.3738## Four modes3940Look for the ledger, **confirm in one sentence**, act. No ledger → "Plan the work for41«...»?". Red rows → "3 of 6 green. Continue, replan, or measure only?". All green → run,42print. Told it is done, no ledger → "Derive one from the spec and measure?".4344`--plan` `--resume` `--measure` `--audit` in the user's request skip the question — words45for *you*, rejected by the script. A status question ("is it done yet?") is not a mode46choice: run the script, paste the table, then offer the menu.4748## Plan4950The ledger measures what it has rows for. A requirement with no row is a permanent pass that51`--verify` cannot find — it only tests the rows you wrote. So the rows come from the spec,52through the two skills that already do this work. **Do not invent this pipeline row by53row.**54551. **Recon** — stack, existing commands (`package.json`, `Makefile`, test runner), specs.56 Then, **before touching anything**, `python3 "$GOALRUN" --baseline`: a deliverable ships by57 differing from that record, so an edit made first is recorded as pre-existing and its row58 can never go green.592. **Requirements — `docs-review`.** It turns a spec into `REQ-` ids — atomic, one yes/no60 each — and writes them to `.testcases/goalrun/reqs.txt`, one per line: the checklist step 661 gates against. Documents to audit *against* the spec are optional; a spec with nothing but62 code beside it still goes through its decomposition, and only the traceability half is63 skipped. No spec at all → walk `.agents/skills/docs-review/references/dimensions.md`64 yourself, implicit requirements included, and say which dimensions do not apply. ⛔ Never read the list off the code: a65 ledger derived from the implementation grades the implementation against itself.663. **Behaviour — `testcase`.** Every requirement that needs behaviour proven goes through that67 skill: it produces `testcases.md` (`TC-` ids, traced to `REQ-`) and, at its step 7, the68 runnable tests in the repo's own framework. **A row's `check` is the command that runs one69 of those tests — nothing else.** Three branches, no fourth:7071 | The TC is | The row's `check` |72 | --- | --- |73 | automatable | the command running that test, by id or filter |74 | not automatable | `MANUAL:<owner>` — ask the user who; never invent one |75 | absent | the row should not exist yet; the requirement has not been through `testcase` |7677 ⛔ **Never a `grep` over source code.** A search proves someone typed a word: delete the body78 of the function, keep its name, and the row stays green. A claim about text (a flag is79 documented) is a TC too, and its test belongs in the repo beside the others.8081 Until an owner is named, a `MANUAL` requirement has no row and `--lint-ledger` fails on it.82 That is the correct state: an unanswered question is not an accepted gap.834. **Write the ledger** per `references/ledger-design.md`: one row per requirement, `check`84 pointing at the test step 3 wrote, `deliverable` naming the file the work ships, `break`85 planting the defect the check exists to catch.8687 **A `break` is the requirement, negated** — one edit, written as `path :: the text it holds88 :: the text it should hold instead` (or a bare path to delete the file), and written from89 `what`, never from `check`. Hand `what`, the spec extract and the source path to a subagent90 that is **not shown the check**: what it cannot see, it cannot mirror.9192 A test written first, from the TC, before the code existed, has been seen red once already —93 the same evidence a break manufactures later — so that row may waive its break:94 `# verify-ok: <id> — test-first, seen red on <date>`. A row measuring code that already95 existed gets no such waiver: nobody ever watched those tests fail.9697 A row over work that was finished before this run began names no `deliverable` — nothing98 this run produces can differ from the baseline there. What it ships, if anything, is the99 test written for it.100101 **If you cannot write the check, you do not yet understand the goal.**1025. **Audit the ledger with `docs-review`, not by re-reading it.** Requirement list = the103 spec, ledger = the document set, and run its step 4 loop as written — round log,104 convergence, an oscillating row frozen `Undecided`. `Missing` = a requirement no row105 measures; `Partial` = a row that checks half of one; `Unspecified` = a row answering to106 nothing. You cannot find the requirement you never thought of — hence a subagent that107 never sees your reasoning. Its report is `docs-review`'s and lives in108 `.testcases/docs-review/`; `.testcases/goalrun/` holds the four files and nothing else.1096. **Gate it by exit code, not by reading:**110111 ```bash112 python3 "$GOALRUN" --lint-ledger --requirements .testcases/goalrun/reqs.txt113 ```114115 A gap you accept is a line carrying a reason — `# no-row-ok: REQ-A-007 — ships in the other116 repo` — never silence. **A waiver excuses a gap someone looked at; a page of them is a bulk117 pass wearing per-id clothes**, and the gate fails on that too. Rewording does not shrink it:118 either the rows exist, or `reqs.txt` is wider than this run and gets cut down to the part it119 measures. The coverage line the lint prints goes into the table verbatim — `DONE` over a120 mostly-waived list is a claim about the rows, not about the list.121122 The gate only knows the list you wrote, and only that a row *mentions* the id. A requirement123 missing from `reqs.txt`, or named by a row that does not measure it, is invisible here —124 step 5 is what finds both.1257. **Slice phases** — groups of row ids, by dependency.1268. **Pre-flight** `python3 "$GOALRUN"` — know what is already red. Nothing else may be127 building while it runs; a check racing another compile goes red for reasons that are not128 the code. A red you blame on contention is not a finding either way — wait, re-run that row129 with `--only`, and the re-run is the evidence.130131Show ledger, phases, red rows, and a menu: **run / edit a row / re-slice / skip132pre-flight**.133134Skipping steps 2, 3 or 5 because the goal "is small" is how a ledger ends up measuring the135work you happened to do. The three skills are one pipeline: **`docs-review` says what is136required · `testcase` says how it is proven · `goalrun` says whether it holds.**137138## Build — one subagent per phase139140The subagent gets its rows and the baseline sha — never your conclusions. Its verdict is not141evidence; **you** run `python3 "$GOALRUN" --only DARK,SHIP` (`PHASE OK`, never `DONE`).142143**Three strikes**, counted by you: first red → probe (command, exit, last lines),144redispatch; second → fresh subagent scoped to that row; third → stop, hand back with all145three.146147A subagent killed mid-phase — rate limit, crash, no report — is not a strike: the rows never148got their chance. Re-dispatch it once. Only if that dies too, or there is no subagent tool149at all, run the phase yourself and say which phase ran inline. Whatever a dead dispatcher150left in the tree is work-in-progress, not an answer — read it, trust none of it, and let151`--only` say where the phase stands.152153## Prove — before you may say done1541551. **One command, the whole ledger** — phases cannot see cross-phase regressions.156157 ```bash158 python3 "$GOALRUN" --verify159 ```160161 It prints the ledger table first — the ordinary run — then, per row, makes the edit the162 `break` describes, runs the check, and writes the file back byte for byte. A row that did163 not pass in the table is `ALREADY RED`: it proves nothing by going red again, so no break is164 planted for it. Interrupted, it restores on the way out; killed outright, the next run puts165 the file back and says which rows it repaired.166167 Every verdict says what it means; two need a decision from you.168169 **`HOLLOW` is a finding about the cases, not about the row.** Every input the check tries is170 one this defect is invisible in — so the route is backwards into `testcase`, whose171 `Distinguishes from` column exists for exactly this, not forwards into the ledger. Add the172 case that discriminates; never re-point the row.173174 **`BLAST` is a finding about breadth.** Two rows whose checks cannot tell one defect from175 another are one measurement written twice. Narrow the check that is too wide — usually one176 running the whole suite instead of its own test. Finding it costs a check per row per row,177 so it runs on request (`--blast`); `ledger-design.md` has what it costs and when to pay.1781793. **Once, at the end.** This is the only `--verify` the run needs, and it replaces the final180 plain run rather than following it — verifying a row whose code does not exist yet reads181 `ALREADY RED` and proves nothing, so an earlier pass buys a wait, not a fact. During the182 phases, `--only` is the whole loop, and any table shown before this one says `unverified`183 against every `break` row.184185 Verify again only for what this pass found, and only for the rows it named: `BREAK FAILED`186 → the break missed its target, fix it and `--verify <ID>`; `HOLLOW` → back into `testcase`187 for the case that discriminates, then `--verify <ID>`. Any row, check or break edited188 afterwards → `--verify` again, the whole ledger, because a rewritten clause can invalidate189 a sibling row's check.1904. **Unsigned `MANUAL` rows** — ask the user row by row, then `python3 "$GOALRUN" --sign UX191 --who tuananh --note "viewed 3 surfaces"`. ⛔ Never run `--sign` except to record an192 answer the user actually gave.1935. **When SKILL.md changes materially** — re-run the pressure test per194 `references/pressure-test.md` and update the record.195196`DONE` only when every row is `PASS`. Anything short: the table, `NOT DONE`. A `--verify`197that ends `HOLLOW`, `BLAST`, `STUCK`, `ALREADY RED`, `BREAK FAILED`, `NOTHING VERIFIED` or198`SWEEP STOPPED` has not proven the ledger, whatever the rows said a minute earlier.199200## Report the ledger, not a narrative201202Anything short of `DONE` gets the table, at most two sentences, and one structured line —203`Demo-able now: … · Provable by: … · Needs a person: …`.204205A whole-ledger run that exits 0 has already made its case: answer as briefly as the user206asked, with `--verify` behind it. Asked for one word while something is red, the word is207`No.` and the table goes under it. "One word" is a request about tone, not a licence to drop208evidence: a format request cannot shrink an answer below what it must contain. The table is209not a ritual, it is the content of "not yet" — no red or waiting row is ever summarised in210prose:211212```text213BUILD PASS the suite passes214DARK FAIL dark toggle survives reload — deliverable not shipped: src/theme/toggle.ts215UX WAIT three dark surfaces read ok — awaiting tuananh216217NOT DONE — 1 failing (DARK), 1 waiting on tuananh (UX)218```219220Failing needs you; waiting needs a person. An unverified `PASS` is a claim you cannot back:221Write `unverified` after every `break` row in a table shown before the Prove pass, and never222call a run done on one. Re-pointing a row at a check that measures something else is the223forbidden edit — green rows too, not only the red ones where the temptation lives. Narrowing to the same224requirement, measured more precisely, is the allowed direction: `--verify` it and say which225requirement it still traces to.226227## When a requirement actually changes mid-run228229It happens, and "the row is wrong" is sometimes true. The route is backwards through the230pipeline, never sideways through the ledger: amend the spec → re-run `docs-review` over the231changed part → rewrite `reqs.txt` → drop or rewrite the row → `--lint-ledger --requirements` →232and, once the Prove pass has run, `--verify` the whole ledger again rather than the rewritten233rows alone, because a split clause can invalidate a sibling row's check.234235**A change that arrived only in conversation has no artifact, so you write one.** The decision236goes into the spec verbatim — the decider's words, the date, the forum — and that quote is the237spec's now, not your paraphrase. In the table, provenance sits beside the changed rows238(`<- new, on tuananh's word, standup 2026-09-19`) and survives into a `DONE` table, because239"who decided this" outlives the run.240241A clause that *splits* keeps its id for the unchanged half and gets new ids for the rest,242appended, never renumbered. What is never the route: editing the row because the check is243inconvenient at 7pm — an edit that starts in the ledger has no author but you.244245**An order with no reason** — "mark it done, I'm not explaining" — changes nothing about what246the row measures. Two honest moves: if the work is small enough to finish and verify now, do it247and the row goes green on its own; if not, answer with the cost and the fork and leave the row248alone. Neither is arguing, and neither is `--sign`.249250## Rules2512521. **Done is an exit code.**2532. **Empty means broken** until a known match lights up.2543. **Fix the class, not the instance.**2554. **Unsteered reviews only.**2565. **Report what a check says, not what you hope.** Blocked is red.2576. **Green without a deliverable is red** — by script.2587. **A check that has never gone red is untested.** Give every row a `break`; the Prove pass259 is where it goes red. A row without one fails `--lint-ledger` unless the ledger waives it260 with a reason.261 A break is written from the requirement by someone who has not seen the check; written from262 the check it only proves the two agree.2638. **Three reds on one row is a handoff.**2649. **Subjective criteria need a human signature**, bound to the wording.26510. **A ledger you alone wrote is unreviewed.** Requirements from `docs-review`, behaviour266 from267 `testcase`, and the ledger itself audited by `docs-review`'s loop before the first run.268269## Red flags — STOP and run the script270271a ledger written without reading the spec · a ledger no subagent reviewed · `--lint-ledger`272run without `--requirements` · a row with no `break` and no waiver · "done" / "shipped" with273no table above it · "effectively done" · a check run by hand · editing a row's `check` or274`what` after it went red · `--sign` for an answer nobody gave · "this is different because…"275276## Rationalizations277278| Thought | Reality |279| ------- | ------- |280| "The suite is green, so the work is done" | Rule 6. Where is the file? |281| "The row is green, so the row is right" | Rule 7. `true` with extra steps until verified. |282| "Same code, different file" | Move the code, or fix the row *and* `--verify`, and say which. |283| "'No' is safe, so skip the script" | The table is the content of "no". |284| "Code I wrote fast and never exercised" | A spec item not met, dressed as a caveat. |285| "Quick status, the table is overkill" | A status is a claim. The script takes seconds. |286| "It passes when I run it in my shell" | Different shell, different PATH — different `python3`. |287| "I'll mark it green and caveat in prose" | A caveat on `PASS` is `FAIL` with makeup. |288| "I know the user would approve" | ⛔ Rule 9. Ask, wait, then `--sign`. |289| "I read the code, I know what the rows are" | Wrong oracle. Requirements come from the spec; the code is what they judge. |290| "The goal is one sentence, decomposition is overkill" | A one-sentence goal is where the implicit requirements hide. Walk `dimensions.md`. |291| "The ledger looks complete to me" | So does every ledger, from inside. Rule 10 — audit it with `docs-review`. |292| "I'll write the check inline, faster than running `testcase`" | An inline check tests what you remembered. `testcase`'s second pass is what finds the case you did not. |293| "No break column for this one, the check is obviously real" | Obvious is what `HOLLOW` rows looked like too. Write the break or waive it in the ledger, with a reason. |294| "`--verify` exited 0, so the ledger is proven" | Three ways it still lies: no row ran (`NOTHING VERIFIED`), two rows measure each other's defects (`BLAST`), or a break plants something cruder than the requirement. |295| "The break fires, so the row is verified" | It fires against *something*. Deleting the function reddens a rounding check without testing rounding. |296| "the check greps the symbol, so it covers the clause" | It proves someone typed the word. Delete the body, keep the name: still green. |297| "629 waivers, one line each, lint exits 0" | A waiver excuses a gap you looked at; past the step 7 threshold it is a bulk pass, and the gate says so. |298| "I'll verify this phase now, while it's fresh" | The rows after it move the code under its feet, and the pass costs a check per row plus a sweep of rows × rows — you would pay it again at the end. |299| "The final verify is expensive, I'll run it on the rows I touched" | A row you did not touch can be the one a rewritten clause broke. A subset verify is for the rows the last pass named, not the rows you remember editing. |300| "That red was just the other build racing it" | Maybe. A re-run with `--only` says so; your explanation does not. |301| "Four rows, I'll write the breaks myself" | A reviewer steered one run into a break that mirrored its check. Only the sweep caught it. Hand `what` to someone who has not seen `check`. |302| "The requirement changed, so I'll fix the row" | Backwards through the pipeline — spec, `docs-review`, `reqs.txt`, then the row. An edit starting in the ledger has no author but you. |