fix(plan): a list joiner no longer ends the read of a plain path below 1.9

Review finding 182ceb93.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-13 12:26:07 +02:00
commit 6b8b0e3f2a
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
2 changed files with 16 additions and 3 deletions

View file

@ -353,7 +353,8 @@ export function onFailureAction(text) {
* in src/parse.mjs") counts only where a list starts (after "and", "or", "+"
* or "&"). Right after the backticked path ("`a.test.mjs` src/b.mjs"), and in
* an entry not opened by one (", the CLI entry src/cli.mjs", round 7), it is
* read as before.
* read as before. A list joiner is no word of description: "`a.mjs` +
* `b.mjs` src/c.mjs" reads all three.
* - 6.4 / regression C: a new line that is no list item and holds only a
* backticked capitalised name ("`LICENSE`") ends the description, and the
* name is a file. On the description's own line it stays no file (round 10).
@ -449,7 +450,7 @@ export function fieldPaths(text) {
if (DESCRIPTION_DASH.test(t)) described = true;
const w = plainWord(t);
const file = isFileWord(w);
if (!file) prose = true;
if (!file && !LIST_JOINER.test(t)) prose = true;
if (described || !file) continue;
if ((w.includes('/') && !(tickedEntry && prose)) || i === 0 || LIST_JOINER.test(tokens[i - 1])) out.push(w);
if (t.endsWith(':')) described = true;

View file

@ -10,7 +10,7 @@ import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { buildPlan } from '../helpers/plan-v18-builder.mjs';
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
import { stepTestsOnly } from '../../lib/parsers/plan-schema.mjs';
import { stepTestsOnly, untickedPaths } from '../../lib/parsers/plan-schema.mjs';
const step = (files) => `- **Files:** ${files}\n- **Changes:** x\n`;
@ -49,6 +49,14 @@ const CONTROLS = [
'`tests/a.test.mjs` — covers `parse`, `Parser`, `ParseError`', ['tests/a.test.mjs']],
['B\'s neighbour: a plain path right after the backticked one is read',
'`tests/a.test.mjs` src/b.mjs', ['tests/a.test.mjs', 'src/b.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "+" is read',
'`src/a.mjs` + `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "&" is read',
'`src/a.mjs` & `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "and" is read',
'`src/a.mjs` and `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain source path after two joined test paths makes the step no tests-only step',
'`tests/a.test.mjs` + `tests/b.test.mjs` src/c.mjs', ['tests/a.test.mjs', 'tests/b.test.mjs', 'src/c.mjs']],
];
for (const [name, files, paths] of CONTROLS) {
@ -59,3 +67,7 @@ for (const [name, files, paths] of CONTROLS) {
`PLAN_STEP_TESTS_ONLY ${testsOnly ? 'fires' : 'stays quiet'} on a 1.8 plan: ${files}`);
});
}
test('6.4 / control: untickedPaths names the plain path after backticked paths joined by "+"', () => {
assert.deepEqual(untickedPaths('`src/a.mjs` + `src/b.mjs` src/c.mjs'), ['src/c.mjs']);
});