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

@ -143,6 +143,76 @@ test('red proof: --cwd in a subdirectory still runs at the repo root (plan paths
} finally { rmSync(dir, { recursive: true, force: true }); }
});
// Review finding 2 (2026-10-06): a start-point copy that cannot run the test at
// all failed "red" for the wrong reason and was called proven. Three guards:
// the copy is a real git checkout, gitignored dependency dirs are linked in, and
// the same copy WITH the change must go green (the control run).
test('red proof: a gitignored dependency the test needs is linked into the start point - no false red', () => {
const { dir, base } = repo('a + b');
try {
writeFileSync(join(dir, '.gitignore'), 'node_modules/\n');
sh(dir, 'git', ['add', '.gitignore']);
sh(dir, 'git', ['commit', '-q', '-m', 'ignore']);
const base2 = sh(dir, 'git', ['rev-parse', 'HEAD']);
mkdirSync(join(dir, 'node_modules', 'dep'), { recursive: true });
writeFileSync(join(dir, 'node_modules', 'dep', 'index.mjs'), 'export const one = 1;\n');
mkdirSync(join(dir, 'tests'));
writeFileSync(join(dir, 'tests', 'dep.test.mjs'), [
"import { test } from 'node:test';",
"import { strict as assert } from 'node:assert';",
"import { one } from '../node_modules/dep/index.mjs';",
"import { sum } from '../lib/sum.mjs';",
"test('dep', () => { assert.equal(sum(one, one), 2); });",
'',
].join('\n'));
// The test already passes without any change: it proves nothing.
const r = proveRed({ cwd: dir, base: base2, cmd: 'node --test tests/dep.test.mjs', tests: ['tests/dep.test.mjs'] });
assert.equal(r.status, 'rejected', JSON.stringify({ status: r.status, reason: r.reason }));
assert.equal(r.reason, 'not-red-at-base');
void base;
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('red proof: the start point is a git checkout - a git-reading command does not fail there for lack of .git', () => {
const { dir, base } = repo('a - b');
try {
addTest(dir);
const r = proveRed({ cwd: dir, base, cmd: 'git status', tests: ['tests/sum.test.mjs'] });
assert.notEqual(r.status, 'proven', JSON.stringify({ status: r.status, reason: r.reason }));
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('red proof: a copy that cannot go green even with the change is "not-run" (environment), never proven', () => {
const { dir, base } = repo('a - b');
try {
mkdirSync(join(dir, 'tests'));
// Red anywhere outside the real working tree, whatever the code says.
writeFileSync(join(dir, 'tests', 'env.test.mjs'), [
"import { test } from 'node:test';",
"import { strict as assert } from 'node:assert';",
"test('env', () => { assert.ok(!process.cwd().includes('voyage-red-proof-')); });",
'',
].join('\n'));
writeFileSync(join(dir, 'lib', 'sum.mjs'), 'export function sum(a, b) { return a + b; }\n');
const r = proveRed({ cwd: dir, base, cmd: 'node --test tests/env.test.mjs', tests: ['tests/env.test.mjs'] });
assert.equal(r.status, 'not-run', JSON.stringify({ status: r.status, reason: r.reason }));
assert.match(r.reason, /control/);
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('red proof: an untracked file the change adds (not a listed test) is part of the result, so the control run goes green', () => {
const { dir, base } = repo('a - b');
try {
addTest(dir);
// The fix lives in a NEW untracked module the step adds.
writeFileSync(join(dir, 'lib', 'add.mjs'), 'export const add = (a, b) => a + b;\n');
writeFileSync(join(dir, 'lib', 'sum.mjs'), "export { add as sum } from './add.mjs';\n");
const r = proveRed({ cwd: dir, base, cmd: TEST_CMD, tests: ['tests/sum.test.mjs'] });
assert.equal(r.status, 'proven', JSON.stringify({ status: r.status, reason: r.reason }));
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('red proof: the start-point run leaves the working tree untouched', () => {
const { dir, base } = repo('a - b');
try {