fix(plan): the Files reader gives 6.3.0's answer in the three known regressions

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

View file

@ -332,6 +332,25 @@ export function onFailureAction(text) {
*
* A backslash is read as a slash, a leading `./` is dropped. Release review of
* 6.3.0, rounds 1-10.
*
* Three answers 6.3.0 gave and 6.3.1 lost are given back, each in its narrow
* form, and the round 7-10 neighbours of each keep 6.3.1's answer:
*
* - 6.4 / regression A: inside a description, a comma entry that opens with a
* plain file with a slash, or with one followed by a colon or a dash, starts
* a new entry ("`a.test.mjs` - new test, src/a.mjs - the fix" names both).
* That is the new-line test without its "alone on the line" branch
* (opensChunk), so "— mocks fs, child_process.spawnSync and path.join" stays
* description (round 9).
* - 6.4 / regression B: in an entry opened by a backticked path, a plain path
* with a slash after a word of description ("`a.test.mjs` covers the parser
* 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.
* - 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).
*/
const SEG = '[\\w@.*?()-]';
const PATH_WORD = new RegExp(`^(?:${SEG}+\\/)+${SEG}*$|^[\\w@*?-][\\w@.*?-]*\\.[A-Za-z*][A-Za-z0-9*]*$|^\\.[\\w-][\\w.-]*$`);
@ -365,6 +384,13 @@ const opensEntry = (tokens) => {
const w = plainWord(tokens[0]);
return isFileWord(w) && (w.includes('/') || tokens.length === 1 || tokens[0].endsWith(':') || DESCRIPTION_DASH.test(tokens[1]));
};
// A comma entry inside a description that opens a new entry: opensEntry's test
// without "alone on the line", so "path.join" after a comma stays description
// (6.4 / regression A).
const opensChunk = (tokens) => {
const w = plainWord(tokens[0]);
return isFileWord(w) && (w.includes('/') || tokens[0].endsWith(':') || DESCRIPTION_DASH.test(tokens[1]));
};
export function fieldPaths(text) {
const spans = [];
@ -382,12 +408,21 @@ export function fieldPaths(text) {
const item = LIST_ITEM.exec(line);
const body = item ? line.slice(item[0].length) : line;
if (item || (described && body.trim() !== '' && opensEntry(body.trim().split(/\s+/)))) described = false;
// A new line that holds only a backticked capitalised name ends the
// description (6.4 / regression C).
const alone = /^\u0000(\d+)\u0000$/.exec(body.trim());
if (alone && BARE_NAME.test(spans[Number(alone[1])])) described = false;
for (const raw of body.split(',')) {
const entry = raw.trim().replace(/^[-*]\s+/, '');
if (entry === '') continue;
const tokens = entry.split(/\s+/);
const first = /^\u0000(\d+)\u0000/.exec(entry);
if (first && isTicked[Number(first[1])]) described = false;
if (described && opensChunk(tokens)) described = false;
// An entry opened by a backticked path: after a word of description a
// plain path with a slash counts only where a list starts (6.4 / regression B).
const tickedEntry = Boolean(first && isTicked[Number(first[1])]);
let prose = false;
// A backticked capitalised name alone in its entry, before any
// description, is a file (rounds 6, 10).
if (first && first[0] === entry && !described && BARE_NAME.test(spans[Number(first[1])])) {
@ -410,8 +445,9 @@ export function fieldPaths(text) {
}
if (DESCRIPTION_DASH.test(t)) described = true;
const w = plainWord(t);
if (!isFileWord(w)) prose = true;
if (described || !isFileWord(w)) continue;
if (w.includes('/') || i === 0 || LIST_JOINER.test(tokens[i - 1])) out.push(w);
if ((w.includes('/') && !(tickedEntry && prose)) || i === 0 || LIST_JOINER.test(tokens[i - 1])) out.push(w);
if (t.endsWith(':')) described = true;
}
continue;