In the planning folder of a feature slice there is now a file called PROMISES.md. It has one row per promise — a button, a popup state, a form field, a requirement clause, a plan deliverable — each with a citation to the artifact that promises it, and three cells recording whether the spec, the plan, and the code carry it. On the branch whose review could not finish, the file holds 274 rows. The verdict over those rows is printed by a script that counts statuses — 239 carried into code with evidence, 36 open — and its output is pasted into the report verbatim. Nothing restates the numbers by hand, because the last article showed what hand-carried totals do over six reports: 41, 31, 33, 23, 24, 35.
Why One Row Per Leaf
The rows are deliberately small. A page is never one row; its leaves are the rows — every control, action, state, and variant a user could recognize. The old loop granted a page-sized "built" verdict to screens whose right-hand column of controls did not exist, and then paid for it one round at a time: an on-page layout picker in round three, a download action in round four, an edit link in round five, each surfacing as "one more" nested gap inside a frame already marked done. At leaf granularity, the enumeration happens once, up front, and a screen with a missing control is a row with an empty cell rather than a surprise.
Row admission kept the old method's best rule: a row exists only if a real artifact promises it, cited by frame, requirement, or plan task. Everything real-but-unpromised — the stale doc, the test that asserts nothing, the query wrapped in the wrong transaction — goes to a separate drop-file, routed to the skill that owns it, and counted nowhere. That rule is what keeps the denominator finite, and it survived unchanged.
Why It Is Frozen Before Anything Is Verified
The file is extracted first, committed, and only then verified. Extraction dispatches one reading pass per source class — design snapshot, spec docs, plan — each returning candidate rows with citations, merged and de-duplicated into the ledger. Fresh passes repeat over the same frozen sources until two consecutive passes add zero rows. That is the only loop left in the method, and it is safe where the old one was not for a boring reason: the sources bound it. A pass cannot widen into concurrency or query performance when its terms of reference are "propose rows only from these files". Healthy extraction decays fast — a run looks like 240 rows, then +22, then +5, then 0, 0. A backstop of twelve passes exists for a run that never decays, and a run that hits it is reported as capped, never presented as a converged inventory. The first production run did exactly that: its report carries the cap instead of a claim.
After the freeze, the row set moves only visibly: a row can be verified, corrected, or demoted to the drop-file with a reason, and the count of what was promised never grows or shrinks silently between passes. Verification itself is per-row and idempotent — check row, record evidence, done — so there is no convergence signal left to protect. The frozen-dispatch guard and the pre-dispatch hook from the old loop were not carried over. They guarded a re-derivation that no longer happens.
What Counts As Present
A row is only present with two pieces of evidence: that the thing exists, and that it is wired, each cited as file and line. A template no controller serves, a stylesheet no manifest imports, a class name the CSS never styles, a button whose route is linked from nowhere — each of those is a feature shipped as a shell, and each records as partial, not done. A cell nobody could verify stays a question mark with a stated reason, which is a more useful answer than a confident tick. And because a stretched citation is the one way a non-finding can sneak into the count, a separate quality pass re-opens every gap row and confirms the cited artifact really promises the thing, demoting the ones where it does not.
The Tool Gets Its Own Test Suite
The part I should have done first: the reviewing tool is now tested the way code is tested. The repository carries two miniature fixture projects with defects planted on purpose — six missing-feature gaps and three decoys in one, three gaps and one decoy in the other. The decoys are realistic problems that fail the admission rule, stale docs and unwired-looking code that is actually wired, and the gate requires them to stay uncounted. Expected ledgers are checked in; a scoring script compares a run against them; and the standing rule is that no change to the skill, its reference files, or its adapters is used on a real project before it passes the fixture gate. Review-skill changes were previously debugged the only place they could be — on live projects, mid-loop, which is how a measurement instrument ends up with fifty-one loop commits of calibration drift.
The Blind Comparison
On 20 August the new skill ran against the same commit of the same branch the old loop had spent nine rounds on. The run never read the old report; a separate pass mapped the two result sets afterwards. Four things came out of the mapping.
It reproduced ten of the old loop's findings, as nineteen rows — the leaf granularity splits what the old loop reported as single items, including the blocked money path. It found seventeen gaps the old loop never surfaced in nine rounds, among them a family of five member-state variants whose screens were promised and absent, the old loop's largest single blind spot. It refused to count eight of the old findings, seven because no artifact promises them — doc drift and process debris that belong to other skills — and one because it was present with citable evidence all along.
And it missed seventeen of the old loop's findings. Most are visual-fidelity items the method excludes by design — styling divergence is a different check, and the verdict now carries a standing line saying so, because an absence of styling findings must never read as styling being fine. The ones that hurt are four or five genuine blockers, all of one shape: the recipes verified that a thing exists and is wired, and never followed the promise one hop into behaviour. A call-to-action can link a route that exists and still lands its audience on a 403. A cancellation can write exactly the state its own reactivation check rejects. Static presence, statically verified, said nothing about either. That miss became the next generation of the fixture gate: three new recipe classes — evidence locality, action chains, state transitions — each with a planted fixture defect, so the class of miss is now a test the skill has to pass.
Kerbe
The method is packaged as a Claude Code plugin called Kerbe — German for the notch cut through the full thickness of a material, which is the geometry a thin vertical slice is named after. The lifecycle skills are the portable part; everything stack-specific, including the "exists and is wired" recipes per framework, lives in swappable adapter files, and the coverage skill ships with its fixture projects so the gate travels with the tool. The plugin is being prepared for public release, and the promise-file design described here is its first skill.
Once the computed count says what is missing, a person still has to read what is there — and how many lines of that reading a slice generates turns out to be predictable before any code exists. That is the last article in this series.