fix(plan): a 1.9 wrapped line keeps its paths only after a comma or in an empty entry

Operator decision 29 (14.10); review findings d21ff81e and eaeb398e.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-14 10:29:35 +02:00
commit b70f242e53
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 115 additions and 21 deletions

View file

@ -339,6 +339,55 @@ test('1.9: two unmarked File lines in a row give both tests', () => {
assert.deepEqual(schema.testFirstFiles(step1Fields(text), '1.9'), ['tests/mod1.test.mjs', 'tests/mod1b.test.mjs']);
});
// ---- Step 2 (second remediation): a 1.9 wrapped line keeps its paths only
// after a comma, or in an entry with nothing on its own line (operator
// decision 29, 14.10; reviews d21ff81e, eaeb398e) ----
test('1.9: a line wrapped onto a backticked call after an entry with no comma is description - no issue', () => {
const r = entryPaths('`tests/a.test.mjs`\n `parse()` reads the input');
assert.deepEqual(r.files, ['tests/a.test.mjs']);
assert.deepEqual(r.issues, []);
});
test('1.9: a line wrapped onto a backticked option after an entry with no comma is description - no issue, and the validator agrees', () => {
const pair = '`src/a.mjs`\n `--strict` makes it an error';
const r = entryPaths(pair);
assert.deepEqual(r.files, ['src/a.mjs']);
assert.deepEqual(r.issues, []);
const v = validatePlanContent(plan19(pair), { strict: true });
assert.deepEqual(pathIssues(v.errors), [], JSON.stringify(v.errors));
});
test('1.9: once a wrapped line is description, a comma at its end does not open the entry again', () => {
const r = entryPaths('`src/a.mjs`\n `src/b.mjs`,\n `src/c.mjs`');
assert.deepEqual(r.files, ['src/a.mjs']);
assert.deepEqual(r.issues, []);
});
test('1.9: a File line wrapped onto a backticked source path gives only the test, and the step is not tests-only', () => {
const file = ' - File: `tests/mod1.test.mjs`\n `src/mod1.mjs` is the parser under test';
const text = planTestFirst19('1.9', file);
assert.ok(text.includes(`${file}\n`), 'the builder writes the raw File line');
assert.deepEqual(schema.testFirstFiles(step1Fields(text), '1.9'), ['tests/mod1.test.mjs']);
const body = `- **Files:** \`tests/mod1.test.mjs\`, \`src/mod1.mjs\`\n- **Changes:** x\n- **Test first:**\n${file}\n - Verifies: x\n`;
assert.deepEqual(schema.stepTestsOnly(body, '1.9'), { testsOnly: false, files: ['tests/mod1.test.mjs', 'src/mod1.mjs'] });
});
test('1.9: a File line wrapped onto a backticked call gives no PLAN_STEP_PATH_NOT_A_PATH under --strict', () => {
const text = planTestFirst19('1.9', ' - File: `tests/mod1.test.mjs`\n `parse()` reads the input');
const r = validatePlanContent(text, { strict: true });
assert.equal(r.errors.find((e) => e.code === 'PLAN_STEP_PATH_NOT_A_PATH'), undefined, JSON.stringify(r.errors));
assert.deepEqual(schema.testFirstFiles(step1Fields(text), '1.9'), ['tests/mod1.test.mjs']);
});
test('1.9 controls: an empty File line takes its first wrapped line, a comma keeps the list, a comma after no path still refuses', () => {
const empty = planTestFirst19('1.9', ' - File:\n `tests/mod1.test.mjs` (new)');
assert.deepEqual(schema.testFirstFiles(step1Fields(empty), '1.9'), ['tests/mod1.test.mjs']);
assert.deepEqual(entryPaths(WRAPPED).files, ['tests/a.test.mjs', 'src/a.mjs']);
const bad = entryPaths('- `src/a.mjs`\n- the CLI,\n `src/b.mjs`');
assert.deepEqual(bad.issues.map((i) => [i.code, i.line]), [['PLAN_STEP_PATH_NOT_FIRST', 2]]);
});
// ---- Step 4 (trial 4): a 1.9 plan refuses a placeholder and a mixed-case bare
// name as a file (operator decision 22, 13.10; review 33f37f53) ----