fix(execute): the helper and the executor give red proof no list
Operator decisions 30 and 31 (14.10); review findings 11ee78c5 and d01df508. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
73e53f3134
commit
a70f082f6e
6 changed files with 224 additions and 43 deletions
|
|
@ -27,24 +27,31 @@ and never touch a file the step does not list.
|
||||||
- `start_sha` — the commit the step starts from. Everything you change is read
|
- `start_sha` — the commit the step starts from. Everything you change is read
|
||||||
later as `git diff {start_sha}`, so the diff is your real report.
|
later as `git diff {start_sha}`, so the diff is your real report.
|
||||||
- `report_path` — where your report goes.
|
- `report_path` — where your report goes.
|
||||||
- `mode` — `build` (the step's first helper) or `fix` (a fix round: the
|
- `mode` — `build` (the step's first helper, or, in the round `build (restart)`,
|
||||||
controller's findings to fix are included; fix only those).
|
a fresh one after the executor corrected its own red-proof call) or `fix` (a
|
||||||
|
fix round: the controller's findings to fix are included; fix only those).
|
||||||
- `voyage_root` — the plugin root, for the CLIs below.
|
- `voyage_root` — the plugin root, for the CLIs below.
|
||||||
|
|
||||||
## Mode `build`
|
## Mode `build`
|
||||||
|
|
||||||
1. **Test first.** Read the step's `Test first:` and write that test (and only
|
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.
|
that test) first. Follow the `Pattern:` file's style when one is named.
|
||||||
|
In the round `build (restart)` the tree holds what the first helper left
|
||||||
|
and nothing is reverted: keep one copy of each test case you write (add no
|
||||||
|
case the test file already holds).
|
||||||
The step's test files are every file on the `Test first:` `File:` line:
|
The step's test files are every file on the `Test first:` `File:` line:
|
||||||
the backticked paths that stand right after `File:`, separated only by
|
the backticked paths that stand right after `File:`, separated only by
|
||||||
commas or spaces; a path in the description after them is not a test file
|
commas or spaces, and below plan_version 1.9 also each further path after
|
||||||
|
a note in brackets such as `(new)` or after `and`, `plus` or `+`, as long
|
||||||
|
as it is clearly a test (the first path that is not ends the list; operator
|
||||||
|
decision 31); a path in the description after them is not a test file
|
||||||
(every plan_version; operator decision 20, 2026-10-12).
|
(every plan_version; operator decision 20, 2026-10-12).
|
||||||
Then register every new file you created so the executor's diff sees it:
|
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:
|
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 as ONE comma-separated argument: a,b> --plan "{plan}" --step {step}`
|
`node {voyage_root}/lib/verification/red-proof.mjs --red-only --base {start_sha} --cmd "<the step's Verify command>" --plan "{plan}" --step {step}`
|
||||||
(`--tests` lists the step's test files. Red proof refuses a list that
|
(Red proof reads the step's test files from the plan and prints the list
|
||||||
differs from the plan's and prints the plan's list as `plan_tests`.)
|
it ran on a `tests:` line; give it no `--tests` (operator decision 30).)
|
||||||
Exit 0 = the test fails at the start point: continue. Exit 1 = the test
|
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
|
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.
|
until it fails for the reason the step exists, and run the check again.
|
||||||
|
|
@ -53,10 +60,9 @@ and never touch a file the step does not list.
|
||||||
`Files:` alone decides this, not what you have changed: the behaviour
|
`Files:` alone decides this, not what you have changed: the behaviour
|
||||||
already exists at the start point, and no test can be red here. Do not rewrite the test and do not build; say so in
|
already exists at the start point, and no test can be red here. Do not rewrite the test and do not build; say so in
|
||||||
the report and stop. The executor sends the step up to the plan.
|
the report and stop. The executor sends the step up to the plan.
|
||||||
Exit 2 = it could not run. When its reason starts `tests-mismatch` (the
|
Exit 2 = it could not run. When its reason starts `plan-unreadable`, the
|
||||||
`--tests` you gave differs from the plan's list), run the gate once more
|
plan path or the step number you were given is wrong: do not build; say
|
||||||
with `plan_tests` as `--tests`, whatever the plan_version; only a second
|
so in the report and stop (the executor corrects its call). Exit 2 otherwise (the
|
||||||
exit 2 goes on to the two branches that follow. Exit 2 otherwise (the
|
|
||||||
Verify command is not one test-runner invocation, or a file is missing):
|
Verify command is not one test-runner invocation, or a file is missing):
|
||||||
on `plan_version` ≥ 1.8 do not build; say so in the report and stop.
|
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
|
Below 1.8, write "red proof not run: {reason}" in the report and go on to
|
||||||
|
|
@ -68,8 +74,8 @@ and never touch a file the step does not list.
|
||||||
3. **Lock the tests** (when the CLI exists):
|
3. **Lock the tests** (when the CLI exists):
|
||||||
`node {voyage_root}/lib/util/test-lock.mjs lock --files <test files> --step {step} --cmd "{Verify command}"`
|
`node {voyage_root}/lib/util/test-lock.mjs lock --files <test files> --step {step} --cmd "{Verify command}"`
|
||||||
(the lock stores the step's test command; only that command can later
|
(the lock stores the step's test command; only that command can later
|
||||||
mark the step green). `--files` is the list red proof accepted: the
|
mark the step green). `--files` is the list red proof printed on its
|
||||||
`--tests` it ran without `tests-mismatch` (after a rerun, `plan_tests`).
|
`tests:` line; copy that line into the report.
|
||||||
If the file does not exist, write "not-locked (test-lock.mjs missing)" in the
|
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
|
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.
|
point: the code changes to meet the test, never the reverse.
|
||||||
|
|
@ -112,7 +118,7 @@ earlier section kept word for word. Never remove or rewrite what is there.
|
||||||
Use the round label the executor gave you in the heading.
|
Use the round label the executor gave you in the heading.
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
## Step {N} - {build | fix round R}
|
## Step {N} - {build | build (restart) | fix round R}
|
||||||
|
|
||||||
### Ran
|
### Ran
|
||||||
- `{command}` -> exit {code}
|
- `{command}` -> exit {code}
|
||||||
|
|
|
||||||
|
|
@ -1451,7 +1451,7 @@ is not proven red at its start point and green at its result does not pass.
|
||||||
skill is the advice; the gate below, `red-proof.mjs`, is the guarantee, and it
|
skill is the advice; the gate below, `red-proof.mjs`, is the guarantee, and it
|
||||||
stays a command whatever the skill says.
|
stays a command whatever the skill says.
|
||||||
|
|
||||||
**The step's test files** are every file on the `Test first:` `File:` line: the backticked paths that stand right after `File:`, separated only by commas or spaces; a path in the description after them is not a test file (every plan_version; operator decision 20, 2026-10-12). `red-proof.mjs --plan --step` reads the same list from the plan and refuses a `--tests` that differs (exit 2, `tests-mismatch`); on that answer the executor runs it once more with the list it printed (`plan_tests`), and a second exit 2 is read as the exit-2 row below says. The lock's `--files` is the list red proof accepted. The
|
**The step's test files** are every file on the `Test first:` `File:` line: the backticked paths that stand right after `File:`, separated only by commas or spaces, and below plan_version 1.9 also each further path after a note in brackets such as `(new)` or after `and`, `plus` or `+`, as long as it is clearly a test (the first path that is not ends the list; operator decision 31); a path in the description after them is not a test file (every plan_version; operator decision 20, 2026-10-12). `red-proof.mjs --plan --step` reads that list from the plan, so neither the helper nor the executor passes `--tests` (operator decision 30, 2026-10-14); it prints the list it ran (`tests`), refuses a `--tests` that differs (exit 2, `tests-mismatch`, the plan's list in `plan_tests`) and a plan or step it cannot read (exit 2, `plan-unreadable`). The lock's `--files` is the list red proof printed as `tests`. The
|
||||||
plan template lists them apart from `Files:`, so everywhere below **the step's
|
plan template lists them apart from `Files:`, so everywhere below **the step's
|
||||||
scope = its `Files:` + its test files**: both are built, both are committed at
|
scope = its `Files:` + its test files**: both are built, both are committed at
|
||||||
the checkpoint, and neither is a scope finding.
|
the checkpoint, and neither is a scope finding.
|
||||||
|
|
@ -1465,16 +1465,16 @@ already rejects such a step (`PLAN_STEP_VERIFY_NOT_RUNNABLE`).
|
||||||
|
|
||||||
1. **The helper proves red before it builds** (Sub-step C): it writes the test
|
1. **The helper proves red before it builds** (Sub-step C): it writes the test
|
||||||
from `Test first:`, runs the gate
|
from `Test first:`, runs the gate
|
||||||
`red-proof.mjs --red-only --base {start_sha} --cmd "{Verify}" --tests {test files, ONE comma-separated argument} --plan "{plan_path}" --step {N}`
|
`red-proof.mjs --red-only --base {start_sha} --cmd "{Verify}" --plan "{plan_path}" --step {N}`
|
||||||
(the test must FAIL at the start point), locks the test files, and only then
|
(the test must FAIL at the start point), locks the test files, and only then
|
||||||
implements. A `tests_only: true` answer stops the helper before it builds
|
implements. A `tests_only: true` or `plan-unreadable` answer stops the helper before it builds
|
||||||
(it does not rewrite the test); the executor's own run below then sends the
|
(it does not rewrite the test); the executor's own run below then sends the
|
||||||
step up to the plan. The plan's `Files:` alone decides `tests_only`. `--tests` names the step's test files, as Sub-step B defines them.
|
step up to the plan, or (`plan-unreadable`) acts as the exit-2 row says. The plan's `Files:` alone decides `tests_only`. Red proof reads the step's test files, as Sub-step B defines them, from the plan.
|
||||||
2. **The executor proves it both ways after the helper returns:**
|
2. **The executor proves it both ways after the helper returns:**
|
||||||
```bash
|
```bash
|
||||||
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/red-proof.mjs --base {start_sha} --cmd "{Verify command}" --tests {test files, ONE comma-separated argument} --plan "{plan_path}" --step {N} --json
|
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/red-proof.mjs --base {start_sha} --cmd "{Verify command}" --plan "{plan_path}" --step {N} --json
|
||||||
```
|
```
|
||||||
`--tests` names the step's test files, as Sub-step B defines them. The test must FAIL against the start point and PASS against the result
|
Red proof reads the step's test files, as Sub-step B defines them, from the plan. The test must FAIL against the start point and PASS against the result
|
||||||
(the working tree), and a **control run** must PASS: the start-point copy
|
(the working tree), and a **control run** must PASS: the start-point copy
|
||||||
with the step's whole change laid on top. The start point is a temp
|
with the step's whole change laid on top. The start point is a temp
|
||||||
`git clone --shared` checked out at `start_sha` (a real git checkout), with
|
`git clone --shared` checked out at `start_sha` (a real git checkout), with
|
||||||
|
|
@ -1487,9 +1487,9 @@ already rejects such a step (`PLAN_STEP_VERIFY_NOT_RUNNABLE`).
|
||||||
|------|-----------------------|----------|
|
|------|-----------------------|----------|
|
||||||
| 0 | `"proven"` | continue |
|
| 0 | `"proven"` | continue |
|
||||||
| 1, `not-red-at-base`, `tests_only: true` | `"rejected"` | every file in the step's `Files:` is a test (the plan validator's `PLAN_STEP_TESTS_ONLY`, the same reading). The plan's `Files:` alone decides: the step's diff is not read, and a path changed outside `Files:` is the controller's scope finding (Sub-step C). The behaviour its tests cover already exists at the start point (an earlier step built it, or it was there before the plan), so the step can never be red and no rewrite of the test can fix that. The plan is what failed: the step goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4), with no fix round — the planner moves the test into the step that builds the behaviour, or makes a test meant to pass from the start the `Regression:` of the step that changes the code |
|
| 1, `not-red-at-base`, `tests_only: true` | `"rejected"` | every file in the step's `Files:` is a test (the plan validator's `PLAN_STEP_TESTS_ONLY`, the same reading). The plan's `Files:` alone decides: the step's diff is not read, and a path changed outside `Files:` is the controller's scope finding (Sub-step C). The behaviour its tests cover already exists at the start point (an earlier step built it, or it was there before the plan), so the step can never be red and no rewrite of the test can fix that. The plan is what failed: the step goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4), with no fix round — the planner moves the test into the step that builds the behaviour, or makes a test meant to pass from the start the `Regression:` of the step that changes the code |
|
||||||
| 1, `not-red-at-base`, otherwise | `"rejected"` | the test passes without the change, so the TEST is wrong: a finding for Sub-step F, and that fix round is the one exception to the lock — the executor releases the step's test lock for it, the fix helper rewrites the test, passes the `--red-only` gate and locks again. `tests_only: null` (the plan or the step could not be read) counts here too |
|
| 1, `not-red-at-base`, otherwise | `"rejected"` | the test passes without the change, so the TEST is wrong: a finding for Sub-step F, and that fix round is the one exception to the lock — the executor releases the step's test lock for it, the fix helper rewrites the test, passes the `--red-only` gate and locks again |
|
||||||
| 1, `not-green-at-result` | `"rejected"` | the change does not make the test pass: a finding for Sub-step F |
|
| 1, `not-green-at-result` | `"rejected"` | the change does not make the test pass: a finding for Sub-step F |
|
||||||
| 2 | `"not-run"` | never a pass and never proven. **`tests-mismatch` first:** a reason that starts `tests-mismatch` (the `--tests` given differs from the plan's `Test first:` `File:` line) is neither the plan's failure nor a finding at first: the executor reruns once with the list red proof printed (`plan_tests`) and reads that answer; only a second exit 2 goes on below. **plan_version ≥ 1.8:** when the reason is the command (the `Verify:` is not one test-runner invocation), the plan is what failed: the step goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4); any other reason (a missing test file, an unresolvable start point, a control run that stays red) is a finding for Sub-step F. **Below 1.8:** a step with no `Test first:`, or whose `Verify:` is not a test-runner command, is tolerated as `"not-run"` — counted in the final report among the steps without proof of red, never called proven; any other reason is a finding |
|
| 2 | `"not-run"` | never a pass and never proven. **`plan-unreadable` first:** a reason that starts `plan-unreadable` means the executor's own `--plan` or `--step` is wrong (a path, a step number), which no helper can fix: the executor corrects `{plan_path}` to the absolute path of the plan file it loaded in Phase 1 and `{N}` to the number in the step's own `### Step N:` heading. When the helper's report shows it built (proven and locked), the executor reruns only its own red proof with the corrected call. When the helper stopped on the same answer before it built, the executor records the restart (progress `red_proof: "not-run"`, the reason in the step's `error`, and a decision-log entry with `cost_if_wrong` "the step's first helper run"), and starts the step again with a fresh build helper (Sub-step C, round label `build (restart)`; `attempts` +1 and `iterations_remaining` −1 as for any launch; the fresh helper starts from the tree as the first helper left it, nothing is reverted, and it keeps one copy of each test case it writes), once, outside the fix rounds (`fix_rounds` does not change); a step whose `error` already holds a `plan-unreadable` restart, on a `--resume` too, does not restart again; a second `plan-unreadable` goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4), and the upward file's question is red proof's reason (the plan path or step heading it could not read), not a step assumption. A `tests-mismatch` (a `--tests` that differs from the plan's list, in `plan_tests`) cannot come from the documented call, which passes no `--tests`; when it does, it is a finding for Sub-step F. **plan_version ≥ 1.8:** when the reason is the command (the `Verify:` is not one test-runner invocation), the plan is what failed: the step goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4); any other reason (a missing test file, an unresolvable start point, a control run that stays red) is a finding for Sub-step F. **Below 1.8:** a step with no `Test first:`, or whose `Verify:` is not a test-runner command, is tolerated as `"not-run"` — counted in the final report among the steps without proof of red, never called proven; any other reason is a finding |
|
||||||
|
|
||||||
The exit code is the verdict, the output text is not. `base.tap` (when the
|
The exit code is the verdict, the output text is not. `base.tap` (when the
|
||||||
runner prints TAP) says what the red was: `fail 1 of 1` on a node test file is
|
runner prints TAP) says what the red was: `fail 1 of 1` on a node test file is
|
||||||
|
|
@ -1497,7 +1497,7 @@ usually a load error, not a named failing test — say so in the step report.
|
||||||
|
|
||||||
**The test lock (K8).** After its red gate the helper runs
|
**The test lock (K8).** After its red gate the helper runs
|
||||||
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs lock --files {test files} --step {N} --cmd "{Verify command}"`.
|
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs lock --files {test files} --step {N} --cmd "{Verify command}"`.
|
||||||
`--files` names the step's test files, as Sub-step B defines them (the list red proof accepted). From then on the guard refuses Write/Edit/NotebookEdit on those files, so the
|
`--files` names the step's test files, as Sub-step B defines them (the list red proof printed as `tests`). From then on the guard refuses Write/Edit/NotebookEdit on those files, so the
|
||||||
code moves to meet the test and never the reverse. The executor reads
|
code moves to meet the test and never the reverse. The executor reads
|
||||||
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs status` and records
|
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs status` and records
|
||||||
`steps.{N}.test_lock = "locked"` when it exits 0 and names the step's files.
|
`steps.{N}.test_lock = "locked"` when it exits 0 and names the step's files.
|
||||||
|
|
@ -1516,7 +1516,7 @@ Launch `subagent_type: "voyage:step-implementer"` with a short `description`
|
||||||
summary: the plan path, `plan_version`, the step number, the step's text
|
summary: the plan path, `plan_version`, the step number, the step's text
|
||||||
verbatim (every field, including `Interface in:`, `Interface out:` and
|
verbatim (every field, including `Interface in:`, `Interface out:` and
|
||||||
`Review focus:` when present), `start_sha`, `report_path`, `mode: build`, the
|
`Review focus:` when present), `start_sha`, `report_path`, `mode: build`, the
|
||||||
round label (`build`, or `fix round R`), `voyage_root: ${CLAUDE_PLUGIN_ROOT}`,
|
round label (`build`, `build (restart)`, or `fix round R`), `voyage_root: ${CLAUDE_PLUGIN_ROOT}`,
|
||||||
and the skill to work by: `skill: test-first` for a build round,
|
and the skill to work by: `skill: test-first` for a build round,
|
||||||
`skill: debugging` for a fix round (S2; Sub-steps B and F).
|
`skill: debugging` for a fix round (S2; Sub-steps B and F).
|
||||||
|
|
||||||
|
|
@ -1836,7 +1836,7 @@ session's effort, Sub-step E).
|
||||||
| `fix_rounds` | Goes to | What happens |
|
| `fix_rounds` | Goes to | What happens |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1, 2, 3 | a fresh helper | First `node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs pending` (the step is no longer seen green; D1 marks it again). Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3, D4 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
|
| 1, 2, 3 | a fresh helper | First `node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs pending` (the step is no longer seen green; D1 marks it again). Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3, D4 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
|
||||||
| 4 | the plan | **After three failed fix rounds the question is the architecture, not a fourth fix.** The step goes back to planning (below). Sub-step B sends two reds here directly, with no fix round: a `"not-run"` caused by the `Verify:` command, and a `not-red-at-base` with `tests_only: true` (a step of only tests). Stop class 4 |
|
| 4 | the plan | **After three failed fix rounds the question is the architecture, not a fourth fix.** The step goes back to planning (below). Sub-step B sends three answers here directly, with no fix round: a `"not-run"` caused by the `Verify:` command, a second `plan-unreadable` (the executor's own call still wrong after one correction), and a `not-red-at-base` with `tests_only: true` (a step of only tests). Stop class 4 |
|
||||||
| 5 | the operator | A step already sent to the plan (`fix_rounds` 4) that fails its control again after re-planning goes to the operator. The executor never builds this step again: on a later `--resume` it only runs the checks (B, D1, D2, D3, D4, E) on whatever the operator changed. If they fail again the step stays at 5 — there is no sixth round, `fix_rounds` never exceeds 5 — and the run stops again. Stop class 4 |
|
| 5 | the operator | A step already sent to the plan (`fix_rounds` 4) that fails its control again after re-planning goes to the operator. The executor never builds this step again: on a later `--resume` it only runs the checks (B, D1, D2, D3, D4, E) on whatever the operator changed. If they fail again the step stays at 5 — there is no sixth round, `fix_rounds` never exceeds 5 — and the run stops again. Stop class 4 |
|
||||||
|
|
||||||
**Back to planning** (`fix_rounds = 4`): save `git diff {start_sha}` to
|
**Back to planning** (`fix_rounds = 4`): save `git diff {start_sha}` to
|
||||||
|
|
@ -1848,7 +1848,7 @@ operator to re-plan the step (`/trekplan` revises it) and run
|
||||||
`/trekexecute --resume`, which launches a fresh `build` helper for the revised
|
`/trekexecute --resume`, which launches a fresh `build` helper for the revised
|
||||||
step (`attempts` +1, `iterations_remaining` −1) and keeps `fix_rounds` at 4. A step whose proof of red cannot run on a
|
step (`attempts` +1, `iterations_remaining` −1) and keeps `fix_rounds` at 4. A step whose proof of red cannot run on a
|
||||||
≥ 1.8 plan because its `Verify:` is not one test-runner invocation goes here
|
≥ 1.8 plan because its `Verify:` is not one test-runner invocation goes here
|
||||||
directly (Sub-step B).
|
directly (Sub-step B), and so does a second `plan-unreadable` after one corrected restart, with red proof's reason as the upward file's question.
|
||||||
|
|
||||||
**The plan's `On failure:` clause** decides what happens around the ladder:
|
**The plan's `On failure:` clause** decides what happens around the ladder:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,17 @@
|
||||||
# 6. lib/verification/red-proof.mjs with --plan/--step and a --tests that
|
# 6. lib/verification/red-proof.mjs with --plan/--step and a --tests that
|
||||||
# differs from the plan's list (exit 2, tests-mismatch).
|
# differs from the plan's list (exit 2, tests-mismatch).
|
||||||
# 7. artifact-commit with an empty-model trailer (exit 1, HEAD unchanged).
|
# 7. artifact-commit with an empty-model trailer (exit 1, HEAD unchanged).
|
||||||
|
# 8. plan-validator on a 1.9 plan whose Files entry wraps onto a line that
|
||||||
|
# opens with a backticked flag (exit 0, no PLAN_STEP_PATH_NOT_A_PATH).
|
||||||
|
# 9. plan-validator on a 1.9 plan whose Test first File line wraps onto a
|
||||||
|
# line that opens with the source path (exit 0, no PLAN_STEP_TESTS_ONLY).
|
||||||
|
# 10. red-proof --red-only with --plan/--step and no --tests in a second temp
|
||||||
|
# repo (exit 0; its JSON tests field is the plan's one test file).
|
||||||
|
# 11. the same with a plan path that does not exist (exit 2, plan-unreadable).
|
||||||
|
# 12. the same as 10 in text form with a --tests that differs (exit 2, the
|
||||||
|
# line " plan_tests: tests/parser.test.mjs").
|
||||||
|
# 13. red-proof --red-only on a 1.8 plan whose File line names two tests,
|
||||||
|
# each with a (new) note (exit 0; tests is both, in order; decision 31).
|
||||||
# Prints "e2e: ok" and exits 0, or names the failed check and exits 1.
|
# Prints "e2e: ok" and exits 0, or names the failed check and exits 1.
|
||||||
# bash 3.2 compatible, ASCII only.
|
# bash 3.2 compatible, ASCII only.
|
||||||
|
|
||||||
|
|
@ -157,5 +168,94 @@ rc=$?
|
||||||
after="$(git -C "$REPO" rev-parse HEAD)"
|
after="$(git -C "$REPO" rev-parse HEAD)"
|
||||||
[ "$before" = "$after" ] || fail "artifact-commit with an empty-model trailer: HEAD moved from $before to $after"
|
[ "$before" = "$after" ] || fail "artifact-commit with an empty-model trailer: HEAD moved from $before to $after"
|
||||||
|
|
||||||
|
# ---- 8. to 13. decisions 29 to 31: wrapped 1.9 lines, red proof without --tests ----
|
||||||
|
|
||||||
|
node --input-type=module -e '
|
||||||
|
import { writeFileSync } from "node:fs";
|
||||||
|
import { buildPlan } from "./tests/helpers/plan-v18-builder.mjs";
|
||||||
|
const dir = process.argv[1];
|
||||||
|
const e2e = { endToEnd: "bash tests/e2e.sh" };
|
||||||
|
writeFileSync(dir + "/p8.md", buildPlan({ version: "1.9", steps: [{
|
||||||
|
filesRaw: "`src/a.mjs`\n `--strict` flag added",
|
||||||
|
}, e2e] }));
|
||||||
|
writeFileSync(dir + "/p9.md", buildPlan({ version: "1.9", steps: [{
|
||||||
|
file: "src/parser.mjs", test: "tests/parser.test.mjs",
|
||||||
|
filesRaw: "`src/parser.mjs`, `tests/parser.test.mjs`",
|
||||||
|
testFirstRaw: " - File: `tests/parser.test.mjs`\n `src/parser.mjs` is the module it drives",
|
||||||
|
}, e2e] }));
|
||||||
|
writeFileSync(dir + "/p13.md", buildPlan({ steps: [{
|
||||||
|
file: "src/parser.mjs",
|
||||||
|
filesRaw: "`src/parser.mjs`, `tests/a.test.mjs`, `tests/b.test.mjs`",
|
||||||
|
testFirstRaw: " - File: `tests/a.test.mjs` (new), `tests/b.test.mjs` (new)",
|
||||||
|
}, e2e] }));
|
||||||
|
' "$TMP" || fail "could not write the plans for checks 8 to 13"
|
||||||
|
|
||||||
|
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/p8.md" 2>&1)"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || fail "8: 1.9 plan with a wrapped Files entry: plan-validator exit $rc, expected 0: $out"
|
||||||
|
if echo "$out" | grep -q 'PLAN_STEP_PATH_NOT_A_PATH'; then
|
||||||
|
fail "8: 1.9 plan with a wrapped Files entry: PLAN_STEP_PATH_NOT_A_PATH in: $out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/p9.md" 2>&1)"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || fail "9: 1.9 plan with a wrapped Test first File line: plan-validator exit $rc, expected 0: $out"
|
||||||
|
if echo "$out" | grep -q 'PLAN_STEP_TESTS_ONLY'; then
|
||||||
|
fail "9: 1.9 plan with a wrapped Test first File line: PLAN_STEP_TESTS_ONLY in: $out"
|
||||||
|
fi
|
||||||
|
|
||||||
|
R="$TMP/repo-red"
|
||||||
|
mkdir -p "$R/src" "$R/tests" || fail "mkdir $R"
|
||||||
|
git -C "$R" init -q || fail "git init (second repo)"
|
||||||
|
git -C "$R" config user.name 'E2E Test'
|
||||||
|
git -C "$R" config user.email 'test@example.invalid'
|
||||||
|
git -C "$R" config commit.gpgsign false
|
||||||
|
git -C "$R" config core.hooksPath "$TMP/hooks-none"
|
||||||
|
echo 'x' > "$R/README.md"
|
||||||
|
git -C "$R" add README.md && git -C "$R" commit -q -m 'init' || fail "initial commit in the second temp repo"
|
||||||
|
echo 'export const parse = (s) => Number(s);' > "$R/src/parser.mjs"
|
||||||
|
for t in parser a b; do
|
||||||
|
cat > "$R/tests/$t.test.mjs" <<'EOT'
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import { strict as assert } from 'node:assert';
|
||||||
|
import { parse } from '../src/parser.mjs';
|
||||||
|
test('parse reads a number', () => { assert.equal(parse('2'), 2); });
|
||||||
|
EOT
|
||||||
|
done
|
||||||
|
|
||||||
|
# tests_equal <json file> <expected JSON array>: exit 0 when the JSON's tests deep-equals it.
|
||||||
|
tests_equal() {
|
||||||
|
node -e '
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const r = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
||||||
|
try { assert.deepStrictEqual(r.tests, JSON.parse(process.argv[2])); } catch (e) { process.exit(1); }
|
||||||
|
' "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/p9.md" --step 1 --json >"$TMP/rp10.json" 2>"$TMP/rp10.err"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || fail "10: red-proof with --plan/--step and no --tests: exit $rc, expected 0: $(cat "$TMP/rp10.json" "$TMP/rp10.err")"
|
||||||
|
tests_equal "$TMP/rp10.json" '["tests/parser.test.mjs"]' \
|
||||||
|
|| fail "10: red-proof tests is not [\"tests/parser.test.mjs\"]: $(cat "$TMP/rp10.json")"
|
||||||
|
|
||||||
|
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/nope.md" --step 1 --json 2>&1)"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 2 ] || fail "11: red-proof with a plan that does not exist: exit $rc, expected 2: $out"
|
||||||
|
echo "$out" | grep -q 'plan-unreadable' \
|
||||||
|
|| fail "11: red-proof with a plan that does not exist: no plan-unreadable in: $out"
|
||||||
|
|
||||||
|
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --tests tests/a.test.mjs --plan "$TMP/p9.md" --step 1 2>&1)"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 2 ] || fail "12: red-proof with a --tests that differs (text): exit $rc, expected 2: $out"
|
||||||
|
echo "$out" | grep -Fqx ' plan_tests: tests/parser.test.mjs' \
|
||||||
|
|| fail "12: red-proof with a --tests that differs (text): no line ' plan_tests: tests/parser.test.mjs' in: $out"
|
||||||
|
|
||||||
|
node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/a.test.mjs tests/b.test.mjs' --plan "$TMP/p13.md" --step 1 --json >"$TMP/rp13.json" 2>"$TMP/rp13.err"
|
||||||
|
rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || fail "13: red-proof on a 1.8 plan with two (new) tests: exit $rc, expected 0: $(cat "$TMP/rp13.json" "$TMP/rp13.err")"
|
||||||
|
tests_equal "$TMP/rp13.json" '["tests/a.test.mjs","tests/b.test.mjs"]' \
|
||||||
|
|| fail "13: red-proof tests is not the two test paths in order: $(cat "$TMP/rp13.json")"
|
||||||
|
|
||||||
echo 'e2e: ok'
|
echo 'e2e: ok'
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -191,10 +191,12 @@ test('review: no sixth round, attempts never reset, a step at 5 is never built a
|
||||||
assert.match(c, /A step at `fix_rounds` 5 is never built again/);
|
assert.match(c, /A step at `fix_rounds` 5 is never built again/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('review: the helper appends its report, and passes tests as one comma-separated list', () => {
|
test('review: the helper appends its report, and gives red proof no --tests', () => {
|
||||||
const agent = read(AGENT);
|
const agent = read(AGENT);
|
||||||
assert.match(agent, /APPEND it to `report_path`/);
|
assert.match(agent, /APPEND it to `report_path`/);
|
||||||
assert.match(agent, /ONE comma-separated argument/);
|
const gate = agent.split('\n').find((l) => l.includes('red-proof.mjs --red-only'));
|
||||||
|
assert.ok(gate, 'the helper has its red-proof --red-only line');
|
||||||
|
assert.ok(!gate.includes('--tests'), `the helper's red-proof line passes --tests: ${gate}`);
|
||||||
assert.match(agent, /A step with no `Test first:`/);
|
assert.match(agent, /A step with no `Test first:`/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ test('1b: plan-critic has it as a finding under Verification gaps, and still 12
|
||||||
|
|
||||||
test('1c: the executor runs red-proof with --plan/--step and sends a tests_only not-red-at-base up to the plan', () => {
|
test('1c: the executor runs red-proof with --plan/--step and sends a tests_only not-red-at-base up to the plan', () => {
|
||||||
const ex = read('commands/trekexecute.md');
|
const ex = read('commands/trekexecute.md');
|
||||||
assert.match(ex, /red-proof\.mjs --base \{start_sha\} --cmd "\{Verify command\}" --tests [^\n]*--plan "\{plan_path\}" --step \{N\}[^\n]*--json/);
|
assert.match(ex, /red-proof\.mjs --base \{start_sha\} --cmd "\{Verify command\}" --plan "\{plan_path\}" --step \{N\}[^\n]*--json/);
|
||||||
assert.match(ex, /`not-red-at-base`, `tests_only: true`[^\n]*straight up to the plan \(Sub-step F, `fix_rounds = 4`, stop class 4\)/);
|
assert.match(ex, /`not-red-at-base`, `tests_only: true`[^\n]*straight up to the plan \(Sub-step F, `fix_rounds = 4`, stop class 4\)/);
|
||||||
// The helper's gate, as the executor describes it, carries the same flags.
|
// The helper's gate, as the executor describes it, carries the same flags.
|
||||||
assert.match(ex, /red-proof\.mjs --red-only --base \{start_sha\} [^\n]*--plan "\{plan_path\}" --step \{N\}/);
|
assert.match(ex, /red-proof\.mjs --red-only --base \{start_sha\} [^\n]*--plan "\{plan_path\}" --step \{N\}/);
|
||||||
|
|
|
||||||
|
|
@ -251,10 +251,15 @@ test('decisions 28 and 31: at 1.9 the two-test line with notes still gives only
|
||||||
|
|
||||||
// ---- one definition in the prose (plan step 11, review cf241526) -----------
|
// ---- one definition in the prose (plan step 11, review cf241526) -----------
|
||||||
// The executor and the step helper define the step's test files once, as
|
// The executor and the step helper define the step's test files once, as
|
||||||
// decision 20 reads them; every other place points at that definition. Red
|
// decisions 20 and 31 read them; every other place points at that definition.
|
||||||
// proof refuses a --tests that differs from the plan (`tests-mismatch`) and
|
// Operator decision 30 (14.10): red proof reads that list from the plan, so
|
||||||
// prints the plan's list (`plan_tests`): the executor and the helper run it
|
// neither the helper nor the executor passes --tests; it prints the list it
|
||||||
// once more with that list, and the lock follows the list red proof accepted.
|
// ran (`tests`), and the lock's --files is that printed list. A plan or step
|
||||||
|
// red proof cannot read is `plan-unreadable`: the helper stops before it
|
||||||
|
// builds, the executor corrects its own call and restarts the step once with
|
||||||
|
// a fresh build helper (`build (restart)`), outside the fix rounds. A --tests
|
||||||
|
// that differs is still refused (`tests-mismatch`, the plan's list in
|
||||||
|
// `plan_tests`), but the documented call never gives one.
|
||||||
|
|
||||||
const EXEC = ['commands', 'trekexecute.md'];
|
const EXEC = ['commands', 'trekexecute.md'];
|
||||||
const HELPER = ['agents', 'step-implementer.md'];
|
const HELPER = ['agents', 'step-implementer.md'];
|
||||||
|
|
@ -276,33 +281,101 @@ test('prose: the decision-20 definition stands exactly once in each file and kee
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prose: the executor definition names tests-mismatch and plan_tests and keeps its line shape', () => {
|
test('prose: the executor definition names decisions 30 and 31, tests-mismatch, plan_tests and plan-unreadable, and keeps its line shape', () => {
|
||||||
const lines = readRel(EXEC).split('\n');
|
const lines = readRel(EXEC).split('\n');
|
||||||
const i = lines.findIndex((l) => l.startsWith("**The step's test files** are"));
|
const i = lines.findIndex((l) => l.startsWith("**The step's test files** are"));
|
||||||
assert.ok(i >= 0, 'the definition line is there');
|
assert.ok(i >= 0, 'the definition line is there');
|
||||||
const def = lines[i];
|
const def = lines[i];
|
||||||
assert.ok(def.includes('operator decision 20'), 'the definition line carries decision 20');
|
assert.ok(def.includes('operator decision 20'), 'the definition line carries decision 20');
|
||||||
|
assert.ok(def.includes('operator decision 30'), 'the definition line carries decision 30');
|
||||||
assert.ok(def.includes('`tests-mismatch`'), 'the definition names tests-mismatch');
|
assert.ok(def.includes('`tests-mismatch`'), 'the definition names tests-mismatch');
|
||||||
assert.ok(def.includes('`plan_tests`'), 'the definition names plan_tests');
|
assert.ok(def.includes('`plan_tests`'), 'the definition names plan_tests');
|
||||||
assert.ok(def.includes('`--files` is the list red proof accepted'), 'the definition says the lock follows the accepted list');
|
assert.ok(def.includes('`plan-unreadable`'), 'the definition names plan-unreadable');
|
||||||
|
assert.ok(def.includes('`--files` is the list red proof printed as `tests`'), 'the definition says the lock follows the printed list');
|
||||||
assert.ok(def.endsWith(' The'), `the definition line still ends with " The": ${def.slice(-40)}`);
|
assert.ok(def.endsWith(' The'), `the definition line still ends with " The": ${def.slice(-40)}`);
|
||||||
assert.ok(lines[i + 1].startsWith('plan template lists them'), `the next line: ${lines[i + 1]}`);
|
assert.ok(lines[i + 1].startsWith('plan template lists them'), `the next line: ${lines[i + 1]}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prose: the executor exit-2 row names tests-mismatch and the rerun with plan_tests', () => {
|
test('prose: both definitions name decision 31 and read past (new), and, plus or + below 1.9', () => {
|
||||||
const row = readRel(EXEC).split('\n').find((l) => l.startsWith('| 2 | `"not-run"`'));
|
for (const rel of [EXEC, HELPER]) {
|
||||||
assert.ok(row, 'the exit-2 row is there');
|
const text = readRel(rel).replace(/\n\s*/g, ' ');
|
||||||
assert.ok(row.includes('`tests-mismatch`'), 'the exit-2 row names tests-mismatch');
|
const at20 = text.search(/operator decision 20(?!\d)/);
|
||||||
assert.ok(row.includes('`plan_tests`'), 'the exit-2 row names plan_tests');
|
const head = text.lastIndexOf('every file on the `Test first:` `File:` line', at20);
|
||||||
|
assert.ok(at20 > 0 && head >= 0, `${rel.join('/')}: the definition is there`);
|
||||||
|
const def = text.slice(head, at20);
|
||||||
|
assert.ok(def.includes('operator decision 31'), `${rel.join('/')}: the definition names operator decision 31`);
|
||||||
|
assert.ok(def.includes('`plus`'), `${rel.join('/')}: the definition names plus`);
|
||||||
|
assert.ok(def.includes('`(new)`'), `${rel.join('/')}: the definition names (new)`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prose: the helper reruns once with plan_tests on tests-mismatch, before the 1.8 branches, and locks the accepted list', () => {
|
test('prose: the executor exit-2 row names plan-unreadable first, the one restart outside the fix rounds, and tests-mismatch with plan_tests', () => {
|
||||||
|
const row = readRel(EXEC).split('\n').find((l) => l.startsWith('| 2 | `"not-run"`'));
|
||||||
|
assert.ok(row, 'the exit-2 row is there');
|
||||||
|
for (const s of ['`plan-unreadable`', '`tests-mismatch`', '`plan_tests`', 'fresh build helper', 'outside the fix rounds',
|
||||||
|
'absolute path', 'reruns only its own red proof', '`build (restart)`', '`cost_if_wrong`', '`attempts`',
|
||||||
|
'`iterations_remaining`', '`error`', '`--resume`']) {
|
||||||
|
assert.ok(row.includes(s), `the exit-2 row names ${s}`);
|
||||||
|
}
|
||||||
|
assert.match(row, /decision[- ]log\s+entry/, 'the exit-2 row records a decision-log entry');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: a second plan-unreadable goes up to the plan (stop class 4 row, Back to planning)', () => {
|
||||||
|
const text = readRel(EXEC);
|
||||||
|
const row4 = text.split('\n').find((l) => l.startsWith('| 4 | the plan |'));
|
||||||
|
assert.ok(row4, 'the stop-class-4 row is there');
|
||||||
|
assert.ok(row4.includes('`plan-unreadable`'), 'the stop-class-4 row names plan-unreadable');
|
||||||
|
const b = text.indexOf('**Back to planning**');
|
||||||
|
assert.ok(b > 0, 'the Back to planning paragraph is there');
|
||||||
|
const end = text.indexOf('\n\n', b);
|
||||||
|
const para = text.slice(b, end < 0 ? undefined : end);
|
||||||
|
assert.ok(para.includes('`plan-unreadable`'), 'the Back to planning paragraph names plan-unreadable');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: the exit-1 otherwise row no longer counts tests_only: null', () => {
|
||||||
|
const row = readRel(EXEC).split('\n').find((l) => l.startsWith('| 1, `not-red-at-base`, otherwise'));
|
||||||
|
assert.ok(row, 'the exit-1 otherwise row is there');
|
||||||
|
assert.ok(!row.includes('tests_only: null'), 'the exit-1 otherwise row no longer says tests_only: null');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: no red-proof call line in either file passes --tests (operator decision 30)', () => {
|
||||||
|
for (const rel of [EXEC, HELPER]) {
|
||||||
|
const calls = readRel(rel).split('\n').filter((l) => l.includes('red-proof.mjs --red-only') || l.includes('red-proof.mjs --base'));
|
||||||
|
assert.ok(calls.length > 0, `${rel.join('/')}: a red-proof call line is there`);
|
||||||
|
for (const l of calls) assert.ok(!l.includes('--tests'), `${rel.join('/')}: the call passes --tests: ${l}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: the helper stops on plan-unreadable before the 1.8 branches and locks the printed list', () => {
|
||||||
const text = readRel(HELPER);
|
const text = readRel(HELPER);
|
||||||
const mm = text.match(/tests-mismatch[^.]*once\s+more[^.]*plan_tests/);
|
const pu = text.match(/plan-unreadable[^.]*do not build/);
|
||||||
assert.ok(mm, 'the helper runs the gate once more with plan_tests on tests-mismatch');
|
assert.ok(pu, 'the helper does not build on plan-unreadable');
|
||||||
assert.match(text, /whatever\s+the\s+plan_version/);
|
|
||||||
const below = text.indexOf('Below 1.8');
|
const below = text.indexOf('Below 1.8');
|
||||||
assert.ok(below > 0, 'the helper still has its Below 1.8 branch');
|
assert.ok(below > 0, 'the helper still has its Below 1.8 branch');
|
||||||
assert.ok(mm.index < below, 'the mismatch sentence stands before "Below 1.8"');
|
assert.ok(pu.index < below, 'the plan-unreadable sentence stands before "Below 1.8"');
|
||||||
assert.match(text, /`--files` is the list red proof accepted/);
|
assert.ok(text.includes('`--files` is the list red proof printed'), 'the lock follows the printed list');
|
||||||
|
assert.ok(text.includes('copy that line into the report'), 'the helper copies the tests line into the report');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: the round label build (restart) is named by the executor prompt, the report heading and the helper build mode', () => {
|
||||||
|
const ex = readRel(EXEC);
|
||||||
|
assert.ok(ex.split('\n').some((l) => l.includes('(`build`, `build (restart)`, or `fix round R`)')), 'the Sub-step C prompt line names build (restart)');
|
||||||
|
const h = readRel(HELPER);
|
||||||
|
assert.ok(h.split('\n').includes('## Step {N} - {build | build (restart) | fix round R}'), 'the helper report heading names build (restart)');
|
||||||
|
const a = h.indexOf('## Mode `build`');
|
||||||
|
const z = h.indexOf('## Mode `fix`');
|
||||||
|
assert.ok(a >= 0 && z > a, 'the helper has both modes');
|
||||||
|
assert.match(h.slice(a, z), /build \(restart\)[^.]*one\s+copy\s+of\s+each\s+test\s+case/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prose: the old rerun wording is gone from both files', () => {
|
||||||
|
const ex = readRel(EXEC);
|
||||||
|
for (const s of ['runs it once more with the list it printed', 'reruns once with the list red proof printed', 'the list red proof accepted']) {
|
||||||
|
assert.ok(!ex.includes(s), `commands/trekexecute.md still says "${s}"`);
|
||||||
|
}
|
||||||
|
const h = readRel(HELPER);
|
||||||
|
assert.doesNotMatch(h, /once\s+more[^.]*plan_tests/);
|
||||||
|
for (const s of ['after a rerun, `plan_tests`', 'the list red proof accepted']) {
|
||||||
|
assert.ok(!h.includes(s), `agents/step-implementer.md still says "${s}"`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue