fix: round 7 of the release review of 6.3.0, for 6.3.1
Round 7 (516dffa..45aa9e1, WARN: 12 MAJOR, 0 BLOCKER) and the fence defect
found while writing its review.md. Each fix began with a test that failed on
0f400d3 (v6.3.0); suite 2208 (2206/0/2), 14 tests more.
- Tests-only (operator 2026-10-11): the plan's Files: alone decides, and the
text says so (red-proof, trekexecute Sub-step B, step-implementer,
HANDOVER-CONTRACTS). red-proof no longer reads the step's diff, so a change
already in the tree when the step began no longer counts against it, and a
plan placed outside .claude/ reads the same. The round-6 test that had the
diff overrule the plan is rewritten to the new rule.
- The Files: reader keeps a plain file beside a backticked path
(", Dockerfile", "the CLI entry src/cli.mjs", "and Makefile"). Measured on
the 520 Files: fields in 182 plans: no field reads differently.
- Red proof reports a command its timeout killed as killed after the timeout,
not "could not start"; the release check tells a timeout from output past
its buffer and from another signal. One reading of a stop
(criteria-runner stoppedBy) for red proof, the criteria runner and the
release check.
- A trailing `# comment` is dropped in a backticked check too, and in a
command passed as written, so the words screened are the words run.
- The release check reads a relative --review in --repo.
- The 15-minute timeout is defined once (criteria-runner.mjs).
- The findings check reads each cited file once per validation.
- One isMain helper (lib/util/is-main.mjs) for every CLI: the 31 copies of
the realpath guard and the 20 modules still on the file:// guard, which
exited 0 having done nothing under a symlink or a path with a space.
- The Bash guard writes its no-one-to-ask refusal once, on a direct condition.
- The findings check ends a ```json block only at a fence on its own line, so
three backticks inside a JSON string no longer end it (also in 6.2.0).
- Brief-conformance notes: a test for test-lock green on exit 127 (it
passed at once; it fails when 127 is read as could-not-run); the prose now
names the git revision `@~1` as the one leading-@ exception.
Not changed: the example plan's steps 1-2 keep their tests-only warning
(operator decision 2026-10-09; the r5 test records it). Brief note 4 (a
Regression: whose runner is not installed goes to the fix rounds) is a
routing question for the operator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f400d3461
commit
29b4ac91b3
65 changed files with 582 additions and 485 deletions
|
|
@ -46,7 +46,7 @@ import {
|
|||
} from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { join, resolve, dirname, basename, relative, isAbsolute, delimiter, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
const TIMEOUT_MS = 180_000;
|
||||
const DEPCRUISE_CONFIGS = ['.dependency-cruiser.cjs', '.dependency-cruiser.js', '.dependency-cruiser.mjs', '.dependency-cruiser.json'];
|
||||
|
|
@ -255,14 +255,6 @@ export function proveRule({ repo, targets = [], plants, rule }) {
|
|||
|
||||
// ---- CLI --------------------------------------------------------------------
|
||||
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const o = { targets: [], plants: [], prove: false, repo: null, rule: null, bad: null };
|
||||
const value = (i, flag) => {
|
||||
|
|
@ -287,7 +279,7 @@ function parseArgs(argv) {
|
|||
return o;
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
if (isMain(import.meta.url)) {
|
||||
const o = parseArgs(process.argv.slice(2));
|
||||
let result;
|
||||
if (o.bad || !o.repo) {
|
||||
|
|
|
|||
|
|
@ -56,8 +56,12 @@ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|||
export const PLAN_HEADING = '## Verification';
|
||||
export const BRIEF_HEADING = '## Success Criteria';
|
||||
export const DEFAULT_HOOK = join(REPO_ROOT, 'hooks', 'scripts', 'pre-bash-executor.mjs');
|
||||
// The one timeout every check Voyage runs is stopped by (15 minutes): red
|
||||
// proof, the done line, the test lock, Phase 7 criteria, regression-check and
|
||||
// the release check's suite (release review of 6.3.0, round 7).
|
||||
export const DEFAULT_TIMEOUT_MS = 900_000;
|
||||
export const DEFAULT_MAX_OUTPUT = 4000;
|
||||
const MAX_BUFFER = 64 * 1024 * 1024;
|
||||
|
||||
// --- parsing ----------------------------------------------------------------
|
||||
|
||||
|
|
@ -151,24 +155,35 @@ export function looksLikeCommand(span) {
|
|||
export function firstCommand(text) {
|
||||
const m = text.match(/`([^`]+)`/);
|
||||
if (!m) return { command: null, reason: 'no-command' };
|
||||
const raw = m[1].trim();
|
||||
const raw = dropComment(m[1]);
|
||||
// Template placeholders (`{exact command}`) are not commands.
|
||||
if (raw === '' || /^\{.*\}$/.test(raw)) return { command: null, reason: 'placeholder' };
|
||||
if (!looksLikeCommand(raw)) return { command: null, reason: 'not-a-command' };
|
||||
return { command: raw, reason: '' };
|
||||
}
|
||||
|
||||
/**
|
||||
* A command without its trailing comment (`npm test # all green`): a shell
|
||||
* drops it, and with no shell (runCommand) its words would reach the screen
|
||||
* and make an honest check NOT RUN (release review of 6.3.0, round 6). One
|
||||
* reading for every place a command comes from: a fence line, a backticked
|
||||
* span (`Verify:`, `Regression:`, `End-to-end:`, a `## Verification` bullet)
|
||||
* and a command passed as written (red proof, the done line, the test lock),
|
||||
* so the words screened are the words run (round 7). Only a `#` that opens a
|
||||
* word on the same line is a comment (`tests/a#b.test.mjs` is a path).
|
||||
*/
|
||||
export function dropComment(command) {
|
||||
return String(command ?? '').replace(/[ \t]+#[^\n]*$/, '').trim();
|
||||
}
|
||||
|
||||
// A command line inside a shell-tagged fence. A console block may prefix the
|
||||
// line with a `$` or `>` prompt; blank and comment-only lines declare nothing.
|
||||
// A `#` root prompt is NOT stripped: it is indistinguishable from a comment,
|
||||
// and running a comment is the worse of the two mistakes. A trailing comment
|
||||
// (`npm test # all green`) is not part of the command: a shell dropped it,
|
||||
// and with no shell (runCommand) its words would reach the screen and make an
|
||||
// honest check NOT RUN (release review of 6.3.0, round 6).
|
||||
// and running a comment is the worse of the two mistakes.
|
||||
function fencedCommand(text) {
|
||||
const line = text.trim();
|
||||
if (line === '' || line.startsWith('#')) return null;
|
||||
return line.replace(/^[$>]\s+/, '').replace(/\s+#.*$/, '');
|
||||
return dropComment(line.replace(/^[$>]\s+/, ''));
|
||||
}
|
||||
|
||||
function parseSection(markdown, heading, prefix) {
|
||||
|
|
@ -233,7 +248,7 @@ export function parseSuccessCriteria(markdown) {
|
|||
export function screenCommand(command, opts = {}) {
|
||||
const hookPath = opts.hookPath ?? DEFAULT_HOOK;
|
||||
const r = spawnSync(process.execPath, [hookPath], {
|
||||
input: JSON.stringify({ tool_name: 'Bash', tool_input: { command } }),
|
||||
input: JSON.stringify({ tool_name: 'Bash', tool_input: { command: dropComment(command) } }),
|
||||
encoding: 'utf8',
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
|
@ -456,8 +471,8 @@ export function runnableCheck(criterion, opts = {}) {
|
|||
|
||||
function screenShape(command, opts, writeTime) {
|
||||
const cwd = resolve(opts.cwd ?? process.cwd());
|
||||
const text = String(command ?? '');
|
||||
if (text.trim() === '') return deny('the command is empty');
|
||||
const text = dropComment(command);
|
||||
if (text === '') return deny('the command is empty');
|
||||
if (/\n/.test(text)) return deny('a newline: one criterion declares one command');
|
||||
const operator = text.match(SHELL_OPERATORS);
|
||||
if (operator) return deny(`a shell operator (${operator[0]}) — one criterion declares one command, unpiped`);
|
||||
|
|
@ -492,7 +507,7 @@ function screenShape(command, opts, writeTime) {
|
|||
|
||||
/** The words of a command: what the screen checks is exactly what runs. */
|
||||
export function commandWords(command) {
|
||||
return String(command ?? '').trim().split(/\s+/);
|
||||
return dropComment(command).split(/\s+/);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -505,7 +520,29 @@ export function commandWords(command) {
|
|||
*/
|
||||
export function runCommand(command, { cwd, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
||||
const [program, ...args] = commandWords(command);
|
||||
return spawnSync(program, args, { cwd, env: commandEnv(), encoding: 'utf8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 });
|
||||
return spawnSync(program, args, { cwd, env: commandEnv(), encoding: 'utf8', timeout: timeoutMs, maxBuffer: MAX_BUFFER });
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a spawnSync run has no exit code of its own, or null when it has one.
|
||||
* spawnSync sets `error` AND `signal` together when it kills the program
|
||||
* itself: ETIMEDOUT for the timeout, ENOBUFS for output past `maxBuffer`. So
|
||||
* the error code is read first, and a bare `error` with no signal is a program
|
||||
* that never started (not installed). Red proof called a timeout "could not
|
||||
* start" and the release check called an overflow a timeout (release review of
|
||||
* 6.3.0, round 7). `started` is false only for a program that never ran.
|
||||
* @returns {{started: boolean, why: string}|null}
|
||||
*/
|
||||
export function stoppedBy(r, { timeoutMs = DEFAULT_TIMEOUT_MS, maxBuffer = MAX_BUFFER } = {}) {
|
||||
const signal = r.signal ?? 'SIGTERM';
|
||||
if (r.error?.code === 'ETIMEDOUT') return { started: true, why: `was killed (${signal}) after the ${Math.round(timeoutMs / 1000)} s timeout` };
|
||||
if (r.error?.code === 'ENOBUFS') {
|
||||
const size = maxBuffer >= 1024 * 1024 ? `${Math.round(maxBuffer / (1024 * 1024))} MB` : `${maxBuffer} bytes`;
|
||||
return { started: true, why: `was killed (${signal}): its output passed the ${size} that is read from it` };
|
||||
}
|
||||
if (r.signal) return { started: true, why: `was killed (${r.signal})` };
|
||||
if (r.error || r.status === null) return { started: false, why: `could not start: ${r.error?.message ?? 'no exit code'}` };
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- running ----------------------------------------------------------------
|
||||
|
|
@ -515,8 +552,9 @@ export function runCommand(command, { cwd, timeoutMs = DEFAULT_TIMEOUT_MS } = {}
|
|||
// One killed by the timeout did start, and a hang is a red (`failed`).
|
||||
function defaultExec(command, { cwd, timeoutMs }) {
|
||||
const r = runCommand(command, { cwd, timeoutMs });
|
||||
if (r.status === null && r.signal === null && r.error) return { status: null, stdout: '', stderr: '', startError: r.error.message };
|
||||
const why = r.signal ? `killed (${r.signal})\n` : '';
|
||||
const stopped = stoppedBy(r, { timeoutMs });
|
||||
if (stopped && !stopped.started) return { status: null, stdout: '', stderr: '', startError: r.error?.message ?? stopped.why };
|
||||
const why = stopped ? `${stopped.why}\n` : '';
|
||||
return { status: r.status, stdout: r.stdout ?? '', stderr: `${r.stderr ?? ''}${why}` };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@
|
|||
// skills/executing-plans/scripts/task-done: the completion line is appended
|
||||
// only when the test command exits 0.
|
||||
|
||||
import { appendFileSync, existsSync, readFileSync, realpathSync } from 'node:fs';
|
||||
import { appendFileSync, existsSync, readFileSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { allowedCommand, runCommand, screenCommand } from './criteria-runner.mjs';
|
||||
import { DEFAULT_TIMEOUT_MS, repoRoot } from './red-proof.mjs';
|
||||
import { DEFAULT_TIMEOUT_MS, allowedCommand, runCommand, screenCommand } from './criteria-runner.mjs';
|
||||
import { repoRoot } from './red-proof.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
export function doneLineRe(step, commit) {
|
||||
const sha = commit ? String(commit).replace(/[^0-9a-f]/g, '') : '[0-9a-f]{40}';
|
||||
|
|
@ -100,9 +100,4 @@ export function main(argv) {
|
|||
return r.status === 'written' ? 0 : r.status === 'failed' ? 1 : 2;
|
||||
}
|
||||
|
||||
function isMain() {
|
||||
try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (process.argv[1] && isMain()) process.exitCode = main(process.argv.slice(2));
|
||||
if (isMain(import.meta.url)) process.exitCode = main(process.argv.slice(2));
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
import { readFileSync, existsSync, realpathSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { resolve, relative, dirname, isAbsolute, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { sliceSteps, parseStepFields } from '../parsers/plan-schema.mjs';
|
||||
import { parseManifest } from '../parsers/manifest-yaml.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
// Files Voyage's own process writes while planning and executing.
|
||||
export const PROCESS_FILES = Object.freeze([
|
||||
|
|
@ -174,15 +174,7 @@ export function sweep({ plan, base, repo = process.cwd(), process: extra = [] })
|
|||
|
||||
// ---- CLI --------------------------------------------------------------------
|
||||
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
if (isMain(import.meta.url)) {
|
||||
const argv = process.argv.slice(2);
|
||||
const o = { process: [] };
|
||||
let bad = null;
|
||||
|
|
|
|||
|
|
@ -15,9 +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 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).
|
||||
// when every file in the step's `Files:` is a test, so the step can never be
|
||||
// red and goes up to the plan. The plan's `Files:` alone decides (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
|
||||
|
|
@ -55,16 +55,17 @@
|
|||
// Borrowed: GitHub Spec Kit (MIT, github/spec-kit@2e941fe) - its test-first
|
||||
// rule is an instruction there; here a command proves it.
|
||||
|
||||
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
|
||||
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { allowedCommand, runCommand, screenCommand } from './criteria-runner.mjs';
|
||||
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
||||
import { DEFAULT_TIMEOUT_MS, allowedCommand, runCommand, screenCommand, stoppedBy } from './criteria-runner.mjs';
|
||||
import { commandEnv } from '../util/command-env.mjs';
|
||||
import { parseStepFields, sliceSteps, stepTestFile, stepTestsOnlyFromFields } from '../parsers/plan-schema.mjs';
|
||||
import { parseStepFields, sliceSteps, stepTestsOnlyFromFields } from '../parsers/plan-schema.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
export const DEFAULT_TIMEOUT_MS = 900_000;
|
||||
// The one timeout, defined in criteria-runner.mjs (release review of 6.3.0, round 7).
|
||||
export { DEFAULT_TIMEOUT_MS };
|
||||
const TAIL = 2000;
|
||||
|
||||
function tap(output) {
|
||||
|
|
@ -96,7 +97,8 @@ const LOAD_ERROR = /^\s*SyntaxError: |ERR_MODULE_NOT_FOUND|does not provide an e
|
|||
function run(cmd, cwd, timeoutMs) {
|
||||
const r = runCommand(cmd, { cwd, timeoutMs });
|
||||
const output = `${r.stdout ?? ''}${r.stderr ?? ''}`;
|
||||
return { exit: r.status, signal: r.signal ?? null, tap: tap(output), tail: output.slice(-TAIL), spawnError: r.error ? r.error.message : null, loadError: LOAD_ERROR.test(output) };
|
||||
const stopped = stoppedBy(r, { timeoutMs });
|
||||
return { exit: r.status, signal: r.signal ?? null, tap: tap(output), tail: output.slice(-TAIL), spawnError: r.error ? r.error.message : null, stopped: stopped?.why ?? null, loadError: LOAD_ERROR.test(output) };
|
||||
}
|
||||
|
||||
/** What the red at the start point was: the test file failing to load, or a test failing. */
|
||||
|
|
@ -112,42 +114,31 @@ const notRun = (reason, extra = {}) => ({ status: 'not-run', reason, ...extra })
|
|||
// (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.
|
||||
//
|
||||
// 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) {
|
||||
// The plan's `Files:` alone decides (operator 2026-10-11): the step's diff is
|
||||
// not read. In the documented flow the helper writes only the test before this
|
||||
// gate, so the diff could never overrule the plan, and a change already in the
|
||||
// tree when the step began (a dirty tree is allowed, trekexecute Sub-step 0)
|
||||
// made a true tests-only step look like it built something (release review of
|
||||
// 6.3.0, round 7). A path the step changed outside `Files:` is the
|
||||
// controller's scope finding (trekexecute Sub-step C, Hard rule 2), not this check's.
|
||||
function testsOnly(o) {
|
||||
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` };
|
||||
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(', ')}` };
|
||||
return { tests_only: stepTestsOnlyFromFields(parseStepFields(step.body)).testsOnly };
|
||||
}
|
||||
|
||||
// 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
|
||||
// A program that could not start (a spawn error: not installed), or one this
|
||||
// run stopped (the timeout, output past the buffer, a signal), is not a test
|
||||
// verdict at all (criteria-runner `stoppedBy`). 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'})`;
|
||||
return null;
|
||||
return r.stopped ? `the command ${r.stopped}` : null;
|
||||
}
|
||||
|
||||
const git = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
|
||||
|
|
@ -241,7 +232,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, cwd, baseSha), ...out };
|
||||
if (base.exit === 0) return { status: 'rejected', reason: 'not-red-at-base', ...testsOnly(o), ...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 };
|
||||
|
||||
|
|
@ -249,6 +240,8 @@ export function proveRed(o) {
|
|||
// copy itself cannot run the test green, so its red proved nothing.
|
||||
applyResult(cwd, tree, baseSha);
|
||||
const control = run(o.cmd, tree, timeoutMs);
|
||||
const controlBroken = unrunnable(control);
|
||||
if (controlBroken) return notRun(`in the control run: ${controlBroken}`, { ...out, control });
|
||||
if (control.exit !== 0) {
|
||||
return notRun('the control run (start-point copy + the change) stayed red: the start point failed for an environment reason, not for the missing change', { ...out, control });
|
||||
}
|
||||
|
|
@ -302,9 +295,4 @@ export function main(argv) {
|
|||
return r.status === 'proven' ? 0 : r.status === 'rejected' ? 1 : 2;
|
||||
}
|
||||
|
||||
function isMain() {
|
||||
try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (process.argv[1] && isMain()) process.exitCode = main(process.argv.slice(2));
|
||||
if (isMain(import.meta.url)) process.exitCode = main(process.argv.slice(2));
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@
|
|||
// left K10 regressions running differently from Phase 7 criteria (release
|
||||
// review of 6.3.0, round 5).
|
||||
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { sliceSteps, parseStepFields } from '../parsers/plan-schema.mjs';
|
||||
import { firstCommand, runCriteria } from './criteria-runner.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
export const REGRESSION_FIELD = 'Regression';
|
||||
const EXIT = Object.freeze({ passed: 0, absent: 0, failed: 1, blocked: 1, unrunnable: 1, error: 2 });
|
||||
|
|
@ -99,12 +99,4 @@ export function main(argv) {
|
|||
return say(checkRegression({ planText, step, cwd }));
|
||||
}
|
||||
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) process.exitCode = main(process.argv.slice(2));
|
||||
if (isMain(import.meta.url)) process.exitCode = main(process.argv.slice(2));
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
import { existsSync, readFileSync, statSync, realpathSync, accessSync, constants } from 'node:fs';
|
||||
import { dirname, join, resolve, isAbsolute, extname, delimiter } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The repository
|
||||
|
|
@ -248,15 +248,7 @@ export function runTool(tool, files, { cwd, timeout = 120_000, env = process.env
|
|||
// CLI
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
if (isMain(import.meta.url)) {
|
||||
const i = process.argv.indexOf('--repo');
|
||||
const start = i !== -1 ? process.argv[i + 1] : null;
|
||||
if (!start) {
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@
|
|||
// Credit: the tiers and "not measured" are agent-skills' constraint-driven
|
||||
// development (MIT, `9d0c60d`); the stop at step end is Cherny's and Kiro's.
|
||||
|
||||
import { existsSync, realpathSync } from 'node:fs';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join, extname } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolveRepo, detectTools, runTool } from './repo-tools.mjs';
|
||||
import { latestPostEditRecords } from '../../hooks/lib/post-edit.mjs';
|
||||
import { runContract } from '../../hooks/lib/contract-cli.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
// NUL-separated and unquoted, so a name with spaces or non-ASCII letters
|
||||
// comes back as it is on disk (git quotes such names by default).
|
||||
|
|
@ -105,15 +105,7 @@ export function stepCheck({ cwd, full = false }) {
|
|||
return { status, root: repo.root, full, files, not_covered: notCovered, checks };
|
||||
}
|
||||
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
if (isMain(import.meta.url)) {
|
||||
const argv = process.argv.slice(2);
|
||||
const i = argv.indexOf('--cwd');
|
||||
const cwd = i !== -1 ? argv[i + 1] : null;
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@
|
|||
// constraint-driven development - mechanized here as a diff reader so a
|
||||
// command, not the model's word, says whether a test was weakened.
|
||||
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { isTestPath } from '../util/test-path.mjs';
|
||||
import { isMain } from '../util/is-main.mjs';
|
||||
|
||||
export { isTestPath };
|
||||
|
||||
|
|
@ -275,9 +275,4 @@ function out(r, json) {
|
|||
return r.status === 'clean' ? 0 : r.status === 'findings' ? 1 : 2;
|
||||
}
|
||||
|
||||
function isMain() {
|
||||
try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
if (process.argv[1] && isMain()) process.exitCode = main(process.argv.slice(2));
|
||||
if (isMain(import.meta.url)) process.exitCode = main(process.argv.slice(2));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue