refactor(simplify): one source per rule, shared test helpers, a faster isolation test [skip-docs]
/simplify over a97efb6..HEAD (the plan_version 1.9 / trial-4 delivery). - coordinator-contract: engine names come from engine-findings ENGINES; one markedMajorCodeReview predicate under both lowering checks; isEngineFinding everywhere; the unreachable branch (c) of illegalBriefLowering folded into (e). - armB workflow: RULE_KEYS = Object.keys(RULE_TIERS); the engine map is a literal pinned equal to ENGINES by the workflow test. - plan-schema: usesFilesGrammar() is the one 1.9 check (validator, sweep, testFirstFiles, stepTestsOnlyFromFields); opensEntry built on opensChunk. - plan-validator: one issue builder for the Files and Test first grammar. - plan-scope-sweep: the 1.9 and the 6.2.0 reader are one if/else. - artifact-commit: TRAILER_RE/trailerProblem no longer exported (no importer); the order comment says the CLI finds the repo root first. - tests: the lock drivers in session-id-isolation (d) run side by side per mode (~100 s -> ~30 s); a shared artifact-repo helper; severityRank and extractFindingsBlock reused; childEnv(env) without a spread; one copy of the PLAN_VERSION_CURRENT check. Plan readers: validator errors/warnings, plannedPaths, fieldPaths, testFirstFiles and stepTestsOnlyFromFields compared byte for byte against HEAD over 510 plans / 1692 steps plus 210 synthetic 1.7-1.9 cases: same. Suite 2384 (2382/0/2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
acfd2eb9b6
commit
ed6699da58
23 changed files with 201 additions and 204 deletions
|
|
@ -53,7 +53,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|||
import {
|
||||
sliceSteps, validatePlanHeadings, extractPlanVersion, extractPlanCeremony,
|
||||
parseStepFields, findSection, splitSections, findModuleCycle, parseMermaid, fencedBlocks,
|
||||
versionAtLeast, PLAN_VERSION_CURRENT, PLAN_VERSIONS_SUPPORTED, CEREMONY_VALUES,
|
||||
versionAtLeast, usesFilesGrammar, PLAN_VERSION_CURRENT, PLAN_VERSIONS_SUPPORTED, CEREMONY_VALUES,
|
||||
ON_FAILURE_ACTIONS, onFailureAction, stepTestsOnlyFromFields, entryPaths, untickedPaths,
|
||||
testFirstFileEntries, testFirstFiles,
|
||||
} from '../parsers/plan-schema.mjs';
|
||||
|
|
@ -77,7 +77,7 @@ const BACKTICKED = /`[^`\n]+`/;
|
|||
function checkSteps(sections, planVersion) {
|
||||
const out = [];
|
||||
let endToEnd = 0;
|
||||
const grammar = versionAtLeast(planVersion, '1.9');
|
||||
const grammar = usesFilesGrammar(planVersion);
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const tf = f.get('Test first');
|
||||
|
|
@ -217,42 +217,38 @@ function checkRecovery(sections, opts, planVersion) {
|
|||
* names no file. Each `Test first:` `File:` line (plan-schema
|
||||
* `testFirstFileEntries`) is one entry under the same grammar.
|
||||
*/
|
||||
// How checkPathGrammar words an issue in each of the two places the grammar applies.
|
||||
const FILES_WORDING = {
|
||||
subject: 'a `Files:` entry', none: '`Files:` names no file', spans: 'an entry are its files',
|
||||
opens: 'each `Files:` entry opens with its backticked path(s)',
|
||||
example: '`` `src/a.mjs` - the parser ``', tail: ' A step that changes no file names none.',
|
||||
};
|
||||
const TEST_FIRST_WORDING = {
|
||||
subject: 'a `Test first:` `File:` line', none: 'a `Test first:` `File:` line names no file',
|
||||
spans: 'the `File:` line are the test files', opens: 'the `File:` line opens with its backticked test file(s)',
|
||||
example: '`` File: `tests/a.test.mjs` (new) ``', tail: '',
|
||||
};
|
||||
|
||||
function pathGrammarIssue(n, code, shown, w) {
|
||||
if (code === 'PLAN_STEP_PATH_NOT_A_PATH') {
|
||||
return issue(code, `Step ${n}: ${w.subject} opens with a backticked span that is no path: ${shown}`,
|
||||
`plan_version 1.9: the leading backticked spans of ${w.spans}; a call (\`parse()\`), an option ` +
|
||||
'or a URL is description and goes after the path.');
|
||||
}
|
||||
return issue('PLAN_STEP_PATH_NOT_FIRST',
|
||||
shown === '' ? `Step ${n}: ${w.none}` : `Step ${n}: ${w.subject} does not open with a backticked path: ${shown}`,
|
||||
`plan_version 1.9: ${w.opens}, separated by commas or spaces; the description follows (${w.example}).${w.tail}`);
|
||||
}
|
||||
|
||||
function checkPathGrammar(sections) {
|
||||
const out = [];
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const { issues } = entryPaths(f.has('Files') ? f.get('Files').text : '');
|
||||
for (const i of issues) {
|
||||
if (i.code === 'PLAN_STEP_PATH_NOT_A_PATH') {
|
||||
out.push(issue('PLAN_STEP_PATH_NOT_A_PATH',
|
||||
`Step ${s.n}: a \`Files:\` entry opens with a backticked span that is no path: ${i.entry}`,
|
||||
'plan_version 1.9: the leading backticked spans of an entry are its files; a call (`parse()`), an option ' +
|
||||
'or a URL is description and goes after the path.'));
|
||||
} else {
|
||||
out.push(issue('PLAN_STEP_PATH_NOT_FIRST',
|
||||
i.entry === ''
|
||||
? `Step ${s.n}: \`Files:\` names no file`
|
||||
: `Step ${s.n}: a \`Files:\` entry does not open with a backticked path: ${i.entry}`,
|
||||
'plan_version 1.9: each `Files:` entry opens with its backticked path(s), separated by commas or spaces; ' +
|
||||
'the description follows (`` `src/a.mjs` - the parser ``). A step that changes no file names none.'));
|
||||
}
|
||||
for (const i of entryPaths(f.has('Files') ? f.get('Files').text : '').issues) {
|
||||
out.push(pathGrammarIssue(s.n, i.code, i.entry, FILES_WORDING));
|
||||
}
|
||||
for (const entry of testFirstFileEntries(f)) {
|
||||
for (const i of entryPaths(entry).issues) {
|
||||
if (i.code === 'PLAN_STEP_PATH_NOT_A_PATH') {
|
||||
out.push(issue('PLAN_STEP_PATH_NOT_A_PATH',
|
||||
`Step ${s.n}: a \`Test first:\` \`File:\` line opens with a backticked span that is no path: ${entry}`,
|
||||
'plan_version 1.9: the leading backticked spans of the `File:` line are the test files; a call ' +
|
||||
'(`parse()`), an option or a URL is description and goes after the path.'));
|
||||
} else {
|
||||
out.push(issue('PLAN_STEP_PATH_NOT_FIRST',
|
||||
entry === ''
|
||||
? `Step ${s.n}: a \`Test first:\` \`File:\` line names no file`
|
||||
: `Step ${s.n}: a \`Test first:\` \`File:\` line does not open with a backticked path: ${entry}`,
|
||||
'plan_version 1.9: the `File:` line opens with its backticked test file(s), separated by commas or ' +
|
||||
'spaces; the description follows (`` File: `tests/a.test.mjs` (new) ``).'));
|
||||
}
|
||||
}
|
||||
for (const i of entryPaths(entry).issues) out.push(pathGrammarIssue(s.n, i.code, entry, TEST_FIRST_WORDING));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
|
@ -369,7 +365,7 @@ export function validatePlanContent(rawText, opts = {}) {
|
|||
warnings.push(...recovery.warnings);
|
||||
|
||||
// The `Files:` grammar (decision 16): refused from 1.9, a warning below it.
|
||||
if (versionAtLeast(planVersion, '1.9')) {
|
||||
if (usesFilesGrammar(planVersion)) {
|
||||
const grammar = checkPathGrammar(sections);
|
||||
if (strict) errors.push(...grammar);
|
||||
else warnings.push(...grammar);
|
||||
|
|
@ -397,7 +393,7 @@ export function validatePlanContent(rawText, opts = {}) {
|
|||
if (stepsFrom !== null) {
|
||||
const appended = sections.filter((s) => Number(s.n) >= stepsFrom);
|
||||
const stepIssues = versionAtLeast(planVersion, '1.8') ? [] : [
|
||||
...checkSteps(appended, planVersion).filter((i) => i.code !== 'PLAN_NO_END_TO_END'),
|
||||
...checkSteps(appended).filter((i) => i.code !== 'PLAN_NO_END_TO_END'),
|
||||
...checkRunnable(text, appended, { cwd: opts.cwd, stepsOnly: true }),
|
||||
];
|
||||
for (const s of appended) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue