fix(park): park from a CRLF diff and say that a staged new file is parked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-26 14:44:33 +01:00
commit afe56899a9
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 59 additions and 6 deletions

View file

@ -1562,7 +1562,7 @@ finding 4):
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/park-attempt-files.mjs --diff {report dir}/step-{N}-upward.diff --step {N} --attempt {attempts}
```
It moves every file the attempt created that is still untracked to the git
It moves every file new in the attempt's diff that is untracked, or staged but not in HEAD, to the git
dir (`voyage/parked/step-{N}-attempt-{attempts}/`, outside the work tree, so
Sub-step C.2 never registers it again): moved, never deleted, never over a file
already parked. Name the `parked` paths and every `kept` path with its reason

View file

@ -7,10 +7,10 @@
// rejected line would have passed Verify, the manifest and the success
// criterion. Only the plan critic caught it.
//
// Before the fresh build the executor parks them: every file the attempt
// created (a `new file mode` entry in step-{N}-upward.diff) that is still
// untracked on disk moves to <git dir>/voyage/parked/step-<N>-attempt-<k>/
// under its own path. The git dir, like the test lock's, is outside the work
// Before the fresh build the executor parks them: it parks every file new in
// the attempt's diff (a `new file mode` entry in step-{N}-upward.diff) that is
// untracked, or staged but not in HEAD; it moves to
// <git dir>/voyage/parked/step-<N>-attempt-<k>/ under its own path. The git dir, like the test lock's, is outside the work
// tree, so a parked file never comes back as a new file of the next build.
// Moved, never deleted, and never over a file already parked there.
// A new file the helper registered --intent-to-add is in the index, not in
@ -56,7 +56,7 @@ export function newFilesInDiff(diffText) {
const unread = [];
let header = null;
let sawHeader = false;
const lines = String(diffText).split('\n').map((l) => l.replace(ANSI_RE, ''));
const lines = String(diffText).split(/\r?\n/).map((l) => l.replace(ANSI_RE, ''));
for (const line of lines) {
if (line.startsWith('diff --git ')) {
sawHeader = true;

View file

@ -10,6 +10,9 @@
// prefix and lists review_version 1.1 as current, and /trekexecute's
// main-merge gate maps it to "not measured" with the merge locked.
// M11 Handover 6 states that only a full 40- or 64-hex reviewed commit is read.
// E22e The park tool parks every file new in the attempt's diff, untracked or
// staged but not in HEAD: its header and /trekexecute's resume paragraph
// no longer say "still untracked".
// No static import of a new export: the file must load at be0b631.
// Fix the SOURCE a pin guards, not the pin.
@ -101,3 +104,29 @@ test('[M11] Handover 6 states the full 40- or 64-hex reviewed commit', () => {
assert.match(gateParagraph(), /`scope_sha_end`[^.]*full 40- or 64-hex/,
'the verdict-gate paragraph of Handover 6 does not state that only a full 40- or 64-hex scope_sha_end is read');
});
// --- E22e --------------------------------------------------------------------------
/** The sentences of `text` (flattened) that say what the park tool moves or parks. */
function parkSentences(text) {
return flat(text).split(/(?<=\.)\s+(?=[A-Z`"*(])/).filter((s) => /\b(moves|parks)\b/.test(s) && /\bfile\b/.test(s));
}
test('[E22e] the park header says untracked or staged but not in HEAD', () => {
const src = read('lib/util/park-attempt-files.mjs');
const header = src.slice(0, src.indexOf('\nimport '));
assert.ok(header.length > 0, 'lib/util/park-attempt-files.mjs has no header before its imports');
const comment = header.split('\n').map((l) => l.replace(/^\/\/ ?/, '')).join('\n');
assert.doesNotMatch(flat(comment), /still untracked/, 'the park header still says the tool parks only files "still untracked"');
const hits = parkSentences(comment);
assert.ok(hits.some((s) => /new in the attempt's diff/.test(s) && /untracked, or staged but not in HEAD/.test(s)),
`no header sentence says every file new in the attempt's diff is parked, untracked, or staged but not in HEAD: ${hits.join(' | ')}`);
});
test('[E22e] the execute command says the same', () => {
const para = between('commands/trekexecute.md', 'A step at `fix_rounds` 4 on `--resume` was re-planned', 'Then launch a fresh');
assert.doesNotMatch(flat(para), /still untracked/, 'the /trekexecute resume paragraph still says the tool moves only files "still untracked"');
const hits = parkSentences(para);
assert.ok(hits.some((s) => /new in the attempt's diff/.test(s) && /untracked, or staged but not in HEAD/.test(s)),
`no /trekexecute sentence says every file new in the attempt's diff is parked, untracked, or staged but not in HEAD: ${hits.join(' | ')}`);
});

View file

@ -488,3 +488,27 @@ test('[M7] an unborn HEAD parks the attempt\'s files', () => {
rmSync(dir, { recursive: true, force: true });
}
});
// --- fix round 4-5b: E22c --------------------------------------------------------
// [E22c] The diff was split on '\n' only: in a CRLF diff every line kept its '\r',
// so no `new file mode` line matched and nothing was parked, with exit 0.
test('[E22c] a CRLF diff parks its new files', async () => {
const mod = await import('../../lib/util/park-attempt-files.mjs');
assert.equal(typeof mod.newFilesInDiff, 'function', 'newFilesInDiff is not exported');
const crlf = DIFF.split('\n').join('\r\n');
assert.ok(crlf.includes('new file mode 100755\r\n'), 'control: the diff has CRLF line ends');
assert.deepEqual(mod.newFilesInDiff(crlf).paths, ['tests/batch.sh', 'tests/cli-stdin.test.mjs'],
'a CRLF diff did not yield its new files');
const dir = repo();
try {
attemptFiles(dir);
const r = run(dir, crlf);
assert.equal(r.status, 0, r.stdout + r.stderr);
assert.deepEqual(JSON.parse(r.stdout).parked, ['tests/batch.sh', 'tests/cli-stdin.test.mjs'], `a CRLF diff parked nothing: ${r.stdout}`);
assert.equal(existsSync(join(dir, 'tests/batch.sh')), false, 'the file is still in the tree');
assert.equal(readFileSync(parkedPath(dir, 'tests/batch.sh'), 'utf8'), 'stale | cat\n', 'the file is not under the parked directory');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});