fix(execute): the step loop survives an adversarial review

An independent review of abc3083 found 15 defects, the two worst verified
in scratch repos. Each fix started with a test that failed.

- Proof of red could be "proven" by the environment: a start point
  unpacked with git archive had no node_modules and no .git, so any test
  failed there. The start point is now a git clone --shared checked out at
  start_sha, gitignored top-level entries are linked in, and a control run
  (start point + the change) must pass, or the result is not-run.
- The Test first file was never committed (the template lists it apart
  from Files:) and leaked into the next step's diff. The step's scope is
  now Files: + test files, staged at the checkpoint.
- New files outside Files: were invisible to every check. Every untracked
  file is registered with --intent-to-add; a revert unregisters them.
- The floor guard paired renames (foo.test.js -> .bak read as clean) and
  missed skipIf and node's t.skip/t.todo; prose files no longer count as
  stubs.
- Phase 7.5 audited from the last step's start_sha; it now uses the run's
  session_start_sha.
- The done line is written after the checkpoint and checked with --commit.
- fix_rounds never passes 5; attempts are never reset; re-control keeps
  the spec/quality verdicts and earlier floor-guard rulings; the closed
  stop list maps every existing stop; failed vs stopped made consistent;
  the controller's effort is said, not assumed.

Tests: 1470 (1468 pass, 0 fail, 2 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-06 20:17:39 +02:00
commit 44dcc779a9
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
11 changed files with 466 additions and 148 deletions

View file

@ -18,7 +18,7 @@ and never touch a file the step does not list.
## What the executor gives you
- `plan` — path to the plan; `step` — the step number; the step's text verbatim
- `plan` — path to the plan; `plan_version`; `step` — the step number; the step's text verbatim
(Files, Changes, Reuses, Test first, Verify, and when present Interface in,
Interface out, Review focus).
- `start_sha` — the commit the step starts from. Everything you change is read
@ -33,20 +33,28 @@ and never touch a file the step does not list.
1. **Test first.** Read the step's `Test first:` and write that test (and only
that test) first. Follow the `Pattern:` file's style when one is named.
Then register every new file you created so the executor's diff sees it:
`git add --intent-to-add <path>` (this stages nothing).
`git add --intent-to-add -- <path>` (this stages nothing).
2. **Prove it red before you build.** Run, from the repository root:
`node {voyage_root}/lib/verification/red-proof.mjs --red-only --base {start_sha} --cmd "<the step's Verify command>" --tests <test files>`
`node {voyage_root}/lib/verification/red-proof.mjs --red-only --base {start_sha} --cmd "<the step's Verify command>" --tests <test files as ONE comma-separated argument: a,b>`
Exit 0 = the test fails at the start point: continue. Exit 1 = the test
already passes at the start point, so it proves nothing: rewrite the test
until it fails for the reason the step exists, and run the check again.
Exit 2 = it could not run (the Verify command is not one test-runner
invocation, or a file is missing): do not build; say so in the report and stop.
invocation, or a file is missing): on `plan_version` ≥ 1.8 do not build;
say so in the report and stop. Below 1.8, write "red proof not run:
{reason}" in the report and go on to step 4 without a lock.
**A step with no `Test first:`** (allowed only below plan_version 1.8):
skip steps 1–3, write "no Test first: no test written, no proof of red" in
the report, and go to step 4.
3. **Lock the tests** (when the CLI exists):
`node {voyage_root}/lib/util/test-lock.mjs lock --files <test files> --step {step}`.
If the file does not exist, write "not-locked (test-lock.mjs missing)" in the
report. Never silent. Once locked you cannot edit the tests, and that is the
point: the code changes to meet the test, never the reverse.
4. **Implement exactly the step's `Changes:`** in the files its `Files:` lists.
4. **Implement exactly the step's `Changes:`** in the files its `Files:` lists
(and its test files from step 1 — nothing else; register any new file
with `git add --intent-to-add -- <path>`).
Read `Reuses:` first. No extra scope, no refactors on the side, no "while I
was here". If the step cannot be done as written, stop and say why in the
report: the executor sends it up to the plan; you do not improvise.
@ -60,6 +68,11 @@ else. The tests stay locked: if a finding says the test itself is wrong, do not
work around it - say so in the report; that goes up to the plan. Run the
step's `Verify:` again.
The one exception: a `not-red-at-base` finding (the test passes without the
change). The executor has released the lock for that round. Rewrite the test
so it fails for the reason the step exists, pass the `--red-only` gate again
(build step 2), and lock it again (build step 3).
## Never
- Never edit a test to make it pass: no deleted assert, no skip, no looser
@ -70,7 +83,12 @@ step's `Verify:` again.
the test command exits 0.
- Never commit, push, merge or tag. Never write outside the repository.
## The report (write it to `report_path`, then answer with one line)
## The report (APPEND it to `report_path`, then answer with one line)
The report file holds every round of the step and the controller's verdicts.
Add your section at the end — with Edit, or by rewriting the file with every
earlier section kept word for word. Never remove or rewrite what is there.
Use the round label the executor gave you in the heading.
```markdown
## Step {N} - {build | fix round R}