fix: round 9 of the release review of 6.3.0, for 6.3.1
Round 9 (0f400d3..a10018f, WARN: 0 BLOCKER, 12 MAJOR): seven findings and three brief notes, by operator choice. Each finding began with a test that failed ona10018fon its assertion; suite 2230 (2228/0/2). - A check refused for its comment (a quote, $, backtick or backslash) keeps its command and its reason, and every reader refuses it out loud: a nested ## Verification bullet stays a criterion (PLAN_VERIFICATION_NOT_RUNNABLE) and a brief SC warns (BRIEF_SC_NOT_RUNNABLE). It was folded into a sub-point or read as prose, a regression froma10018f. Readers that took a command as runnable (the end-to-end lists) skip a refused one. - The verdict gate reads the last fenced block's info string and body from one reading, and exits 2 unless the info string is exactly json: a last block opened as ```json {x} held a BLOCKER the gate passed with exit 0 (also in 6.3.0). A last block never closed is exit 2 too. - One fence reader (findings-schema fencedBlocks) for the gate and every trailing-json reader: a fence closes only on a run at least as long as its opener, as in Markdown, so a four-backtick example of a json block is one block; CRLF and an indented fence read alike in the gate and the readers. - Files: a description runs on past a comma and onto the next line until a new entry starts (a list item on a new line, or a backticked path), and a plain path followed by a colon starts its description. Measured over all 939 Files: fields in the .md files under the marketplace and the test repo (560 of them in the 48 plan.md files outside worktrees): 0 read differently, tests-only changed in 0. - The test lock compares --cmd by its words, so other blanks between the same words are the step's command. - release-check decides "two files" on real paths: one review.md reached through a symlinked --repo is one file. - Brief notes: red proof's stopped control run has its own test (green at once, red with the branch deleted); the two tests that assumed jest is not installed run with a PATH that finds only git (red ona10018fwith a jest on PATH, green now); the Files: reader's doc states the measured counts (520 fields in 48 plan files at rounds 7-8, not "182 plans"; 560 now). Not in this round (operator 2026-10-11): a144e9ce, 1dc2e785, dc3a467e, 2fa88853, 26873fa5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a10018f9d3
commit
a1e0e50451
11 changed files with 416 additions and 104 deletions
|
|
@ -155,6 +155,11 @@ const COMMENT_NOT_PLAIN = 'a trailing comment with a quote, $, backtick or backs
|
|||
// The first backtick-delimited span on the line is the command by convention —
|
||||
// both templates put it first and a second span holds the expected output.
|
||||
// A plan step's `Verify:` and `End-to-end:` fields follow the same convention.
|
||||
// A `reason` says why the span cannot run. It comes with `command: null` when
|
||||
// the span names nothing to run, and beside the command when only its comment
|
||||
// is refused: that span does carry a command, so it stays a criterion of its
|
||||
// own and is refused out loud (`refusal`), never read as prose or as a
|
||||
// sub-point (release review of 6.3.0, round 9).
|
||||
export function firstCommand(text) {
|
||||
const m = text.match(/`([^`]+)`/);
|
||||
if (!m) return { command: null, reason: 'no-command' };
|
||||
|
|
@ -169,10 +174,16 @@ export function firstCommand(text) {
|
|||
// (`--cmd "{Verify command}"`): there a `"` ends the argument and a `$`, a
|
||||
// backtick or a backslash still means something (release review of 6.3.0,
|
||||
// round 8). A comment with one of them is refused, never dropped.
|
||||
if (/["$`\\]/.test(span.slice(raw.length))) return { command: null, reason: COMMENT_NOT_PLAIN };
|
||||
if (/["$`\\]/.test(span.slice(raw.length))) return { command: raw, reason: COMMENT_NOT_PLAIN };
|
||||
return { command: raw, reason: '' };
|
||||
}
|
||||
|
||||
/** Why a criterion cannot run (no command, or a refused one), or '' when it may be screened. */
|
||||
export function refusal(criterion) {
|
||||
if (!criterion?.command) return criterion?.reason || 'no command';
|
||||
return criterion.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
|
||||
|
|
@ -477,7 +488,8 @@ export function allowedCommand(command, opts = {}) {
|
|||
* what adds it. Returns `{runnable, reason}`.
|
||||
*/
|
||||
export function runnableCheck(criterion, opts = {}) {
|
||||
if (!criterion?.command) return { runnable: false, reason: criterion?.reason || 'no command' };
|
||||
const refused = refusal(criterion);
|
||||
if (refused) return { runnable: false, reason: refused };
|
||||
const r = screenShape(criterion.command, opts, true);
|
||||
return { runnable: r.allowed, reason: r.reason };
|
||||
}
|
||||
|
|
@ -596,8 +608,9 @@ export function runCriteria(criteria, opts = {}) {
|
|||
|
||||
return criteria.map((c) => {
|
||||
const base = { label: c.label, text: c.text, command: c.command };
|
||||
if (!c.command) {
|
||||
return { ...base, status: 'unrunnable', exitCode: null, output: `not runnable: ${c.reason || 'no command'}` };
|
||||
const refused = refusal(c);
|
||||
if (refused) {
|
||||
return { ...base, status: 'unrunnable', exitCode: null, output: `not runnable: ${refused}` };
|
||||
}
|
||||
const permission = allow(c.command, { cwd });
|
||||
if (!permission.allowed) {
|
||||
|
|
@ -649,13 +662,14 @@ export function summarize(results, opts = {}) {
|
|||
return summary;
|
||||
}
|
||||
|
||||
/** The commands a plan's steps declare in `End-to-end:` (first backticked span each). */
|
||||
/** The commands a plan's steps declare in `End-to-end:` (first backticked span each; a refused one is none). */
|
||||
export function endToEndCommands(markdown) {
|
||||
return sliceSteps(String(markdown ?? '').replace(/\r\n?/g, '\n'))
|
||||
.map((s) => parseStepFields(s.body).get('End-to-end'))
|
||||
.filter(Boolean)
|
||||
.map((f) => firstCommand(f.text).command)
|
||||
.filter(Boolean);
|
||||
.map((f) => firstCommand(f.text))
|
||||
.filter((c) => !refusal(c))
|
||||
.map((c) => c.command);
|
||||
}
|
||||
|
||||
const normalizeCommand = (c) => String(c).trim().replace(/\s+/g, ' ');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue