fix: round 5 of the release review of 6.3.0, the structural fix
Round 5 (v6.2.0..d37e538, WARN: 11 MAJOR, 2 MINOR). The patching did not converge (the same two places every round), so the operator chose a structural fix on 2026-10-11. Each change behind a test that failed first. - A check runs with no shell. criteria-runner's runCommand starts the runner with the very words the screen checked (spawnSync(words[0], words.slice(1)), without NODE_TEST_CONTEXT); Phase 7 criteria, Regression: (its exec copy is gone, MINOR 0e98d5c7), proof of red, the done line and the test lock all run through it. No quote, glob, tilde or variable can rewrite a checked word any more. The screen stays and says why: a word that means something else in a shell (quote, backslash, $, brace, leading ~) is refused; a glob reaches only node --test, which expands its own patterns (sh read `tests/.?/` as `tests/../`); no path climbs out with .. or starts at /; a word starting with @ is refused (pytest reads @file as more arguments). - The K6 plan-file sweep has v6.2.0's own Files: reader again (516dffa): backticked paths, and plain words only in a field without them. The shared reader from round 3 planned directories a description named and dropped paths v6.2.0 planned. fieldPaths stays for the tests-only check, with a path test on spans (`parse()`, `p.parse()` are no paths), plain descriptions ignored when the field has backticked paths, dotfiles, backslash paths and glued spans read. - Skill frontmatter reads a value as YAML does: trailing space is nothing, and a comment ends a plain value, a flow list or a block indicator (`model: opus # pinned`). A comment that cuts a plain description, or is the whole value, is still a hazard. - release-check reads the diff with --no-renames: a test moved into an allowed place after the review shows as removed. - The example plan's step 2 heading no longer says "must FAIL initially"; steps 1-2 keep the tests-only warning (operator 2026-10-09,73262b6). Open: MINOR c1fef1ad (one shared isMain helper) is not in this round. Measured: 0 of 463 Files: fields with backticked paths (520 fields in 182 plans across the marketplace repos) carry another path-like word. Suite 2185 (2183/0/2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d37e538ddc
commit
c3641ffa42
17 changed files with 409 additions and 111 deletions
|
|
@ -26,24 +26,17 @@
|
|||
// The runner reports `exitCode`; this CLI writes `exit_code`, the key
|
||||
// progress.json uses (C15). Nothing else is translated.
|
||||
//
|
||||
// The command runs the way the runner runs it (`/bin/sh -c`), but without
|
||||
// NODE_TEST_CONTEXT in its environment (lib/util/command-env.mjs): measured
|
||||
// 2026-10-08, a red `node --test` regression read as passed when this CLI was
|
||||
// itself started under `node --test`, because the nested run reported to the
|
||||
// parent and exited 0.
|
||||
// The command runs exactly as the runner runs a criterion: its own run, with
|
||||
// no shell and without NODE_TEST_CONTEXT (criteria-runner.mjs `runCommand`).
|
||||
// This file had a copy of that code, and a fix made in one copy would have
|
||||
// left K10 regressions running differently from Phase 7 criteria (release
|
||||
// review of 6.3.0, round 5).
|
||||
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
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 { commandEnv } from '../util/command-env.mjs';
|
||||
|
||||
function exec(command, { cwd, timeoutMs }) {
|
||||
const r = spawnSync('/bin/sh', ['-c', command], { cwd, encoding: 'utf8', timeout: timeoutMs, env: commandEnv() });
|
||||
return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
||||
}
|
||||
|
||||
export const REGRESSION_FIELD = 'Regression';
|
||||
const EXIT = Object.freeze({ passed: 0, absent: 0, failed: 1, blocked: 1, unrunnable: 1, error: 2 });
|
||||
|
|
@ -54,7 +47,7 @@ const withProgress = (r) => ({ ...r, progress_status: PROGRESS[r.status] ?? 'not
|
|||
* @param {{ planText: string, step: number, cwd?: string, run?: Function }} o
|
||||
* @returns {{ step: number, status: string, command: string|null, exit_code: number|null, output: string, exit: 0|1|2 }}
|
||||
*/
|
||||
export function checkRegression({ planText, step, cwd = process.cwd(), run = (c, o) => runCriteria(c, { ...o, exec }) }) {
|
||||
export function checkRegression({ planText, step, cwd = process.cwd(), run = (c, o) => runCriteria(c, o) }) {
|
||||
const base = { step, command: null, exit_code: null, output: '' };
|
||||
const found = sliceSteps(planText).find((s) => Number(s.n) === step);
|
||||
if (!found) return withProgress({ ...base, status: 'error', output: `no "### Step ${step}:" in the plan`, exit: EXIT.error });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue