fix: round 6 of the release review of 6.3.0
Round 6 (v6.2.0..c3641ff, WARN: 11 MAJOR, 3 MINOR; 0 BLOCKER). The round-5
fixes hold (code-correctness-reviewer); three of the new MAJOR were side
effects of running checks with no shell. Each fixed behind a test that
failed first on c3641ff.
- git checks stay in the repo too: no path that climbs out with .., starts
at / or reads <rev>:../x, and no --no-index (both reviewers).
- A trailing `# comment` on a shell-fence line is dropped: with no shell,
its words made an honest check NOT RUN, among them the first check of the
shipped example plan.
- A glob reaches `node --test` only on Node 21 and later, which expands it
itself; on an older Node it is refused with that reason (engines >=18).
- With no shell, exit 126/127 is the test program's own: red proof reads it
as a red (the control run still tells an environment fault apart), the
done line and the test lock as a failure.
- A runner that could not start (not installed) is NOT RUN in the criteria
runner and not-run (exit 2) in regression-check, as red-proof and
done-line already said; never a red suite.
- Tests-only: a backticked `Makefile` alone in its entry is a path, and red
proof confirms the plan's tests-only reading against the step's own diff
(the altitude finding: the diff is a fact, the Files: prose is not).
- release-check stops a suite that never ends (15 minutes) and reads it as
not measured (exit 2).
- The two lines this release added to pre-bash-executor.mjs are ASCII.
- MINOR: git and the other runners share one plain-word pattern.
Not in this round: MINOR f00a2eb8 (shared norm/ticked-path and isMain
helpers) and MINOR on plan-review-dedup's repeated tokenizing.
Suite 2194 (2192/0/2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c3641ffa42
commit
45aa9e107a
17 changed files with 262 additions and 62 deletions
|
|
@ -15,8 +15,9 @@
|
|||
// run, and only the full run is authoritative.
|
||||
//
|
||||
// `--plan <plan.md> --step <N>` adds `tests_only` to a `not-red-at-base`: true
|
||||
// when every file in the step's `Files:` is a test, so the step can never be
|
||||
// red and goes up to the plan (see `testsOnly` below).
|
||||
// when every file in the step's `Files:` is a test and so is every file the
|
||||
// step changed since its start point, so the step can never be red and goes
|
||||
// up to the plan (see `testsOnly` below).
|
||||
//
|
||||
// Exit 0 = proven, 1 = rejected (`not-red-at-base` or `not-green-at-result`),
|
||||
// 2 = could not run (`not-run`: no test files, an unknown start point, a command
|
||||
|
|
@ -57,11 +58,11 @@
|
|||
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { allowedCommand, runCommand, screenCommand } from './criteria-runner.mjs';
|
||||
import { commandEnv } from '../util/command-env.mjs';
|
||||
import { sliceSteps, stepTestsOnly } from '../parsers/plan-schema.mjs';
|
||||
import { parseStepFields, sliceSteps, stepTestFile, stepTestsOnlyFromFields } from '../parsers/plan-schema.mjs';
|
||||
|
||||
export const DEFAULT_TIMEOUT_MS = 900_000;
|
||||
const TAIL = 2000;
|
||||
|
|
@ -110,23 +111,42 @@ const notRun = (reason, extra = {}) => ({ status: 'not-run', reason, ...extra })
|
|||
// the executor sends the step up to the plan instead of into fix rounds
|
||||
// (voyage-lekerepo trial 3, 2026-10-09). `null` = the plan or the step could
|
||||
// not be read; without `--plan` the field is absent and nothing new happens.
|
||||
function testsOnly(o) {
|
||||
//
|
||||
// The plan's `Files:` is prose, read by a reader that can misread it; the
|
||||
// step's own diff is a fact. So a step the plan calls tests-only is so only
|
||||
// when every file it changed since the start point is a test too (release
|
||||
// review of 6.3.0, round 6). Before building (`--red-only`) the diff holds the
|
||||
// step's test alone, so there the plan decides. The plan file itself and
|
||||
// Voyage's own files under .claude/ (progress, approvals) are not the step's change.
|
||||
function testsOnly(o, cwd, baseSha) {
|
||||
if (!o.plan) return {};
|
||||
let text;
|
||||
try { text = readFileSync(o.plan, 'utf8'); }
|
||||
catch (e) { return { tests_only: null, tests_only_note: `cannot read the plan: ${e.message}` }; }
|
||||
const step = sliceSteps(text).find((s) => Number(s.n) === Number(o.step));
|
||||
if (!step) return { tests_only: null, tests_only_note: `no "### Step ${o.step}:" in the plan` };
|
||||
return { tests_only: stepTestsOnly(step.body).testsOnly };
|
||||
const fields = parseStepFields(step.body);
|
||||
if (!stepTestsOnlyFromFields(fields).testsOnly) return { tests_only: false };
|
||||
const isTest = stepTestFile(fields);
|
||||
const given = new Set(o.tests ?? []);
|
||||
const changed = [
|
||||
...lines(git(cwd, ['diff', '--name-only', '--no-renames', baseSha]).stdout),
|
||||
...lines(git(cwd, ['ls-files', '--others', '--exclude-standard']).stdout),
|
||||
];
|
||||
const planRel = relative(cwd, realpathSync(o.plan)).split(sep).join('/');
|
||||
const other = [...new Set(changed)].filter((p) => p !== planRel && !p.startsWith('.claude/') && !given.has(p) && !isTest(p));
|
||||
if (other.length === 0) return { tests_only: true };
|
||||
return { tests_only: false, tests_only_note: `the plan's Files: names only tests, but the step's diff also changes ${other.slice(0, 5).join(', ')}` };
|
||||
}
|
||||
|
||||
// A program that could not start (a spawn error: not installed), a signal (the
|
||||
// timeout), or 126/127 from a script that could not run its own command is not
|
||||
// a test verdict at all.
|
||||
// A program that could not start (a spawn error: not installed) or a signal
|
||||
// (the timeout) is not a test verdict at all. With no shell, an exit 126/127
|
||||
// is the test program's own (a script whose tool the step has not built yet):
|
||||
// a red like any other, and the control run tells an environment fault apart
|
||||
// (release review of 6.3.0, round 6).
|
||||
function unrunnable(r) {
|
||||
if (r.spawnError) return `the command could not start: ${r.spawnError}`;
|
||||
if (r.exit === null) return `the command was killed (${r.signal ?? 'timeout'})`;
|
||||
if (r.exit === 126 || r.exit === 127) return `the command could not be run (exit ${r.exit})`;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +241,7 @@ export function proveRed(o) {
|
|||
const baseBroken = unrunnable(base);
|
||||
if (baseBroken) return notRun(`at the start point: ${baseBroken}`, { base, result, linked });
|
||||
const out = { base_sha: baseSha, linked, base, result };
|
||||
if (base.exit === 0) return { status: 'rejected', reason: 'not-red-at-base', ...testsOnly(o), ...out };
|
||||
if (base.exit === 0) return { status: 'rejected', reason: 'not-red-at-base', ...testsOnly(o, cwd, baseSha), ...out };
|
||||
if (o.redOnly) return { status: 'proven', reason: 'red at the start point (--red-only: the result was not run)', red_kind: redKind(base), base_sha: baseSha, linked, base };
|
||||
if (result.exit !== 0) return { status: 'rejected', reason: 'not-green-at-result', ...out };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue