From b2d50ffd833bc8906446fbe28a1be52485c2093e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 9 Sep 2026 21:53:37 -0400 Subject: [PATCH] fix(#4489): make capability-registry.test.cjs's extractShellBlocks CRLF-safe (#4584) * fix(#4489): make capability-registry.test.cjs's extractShellBlocks CRLF-safe Second, independent copy of the #4409 CRLF-fragile line-splitting bug, explicitly flagged as out of scope there ("other duplicated helper in file sibling test files not part of the shadowing chain, tracked separately if divergent"). Same fix: content.split('\n') -> content.split(/\r?\n/), matching src/text-lines.cts's splitLines() and the already-fixed sibling copy in tests/runtime-launcher-parity.test.cjs. Co-Authored-By: Claude Sonnet 5 * chore(#4488): regenerate INVENTORY-MANIFEST.json for tdd-red-evidence.cjs's ADR-457 untracking Discovered while validating #4489's push: #4488's merge (untracking gsd-core/bin/lib/tdd-red-evidence.cjs per ADR-457) left docs/INVENTORY- MANIFEST.json stale, since that file was previously listed as a tracked shipped artifact. Removed via node scripts/gen-inventory-manifest.cjs --write. docs/INVENTORY.md already described this file as gitignored (no update needed there -- it already documented the intended state). Co-Authored-By: Claude Sonnet 5 * revert: undo incorrect INVENTORY-MANIFEST.json edit from 57f1457a27 The prior commit removed tdd-red-evidence.cjs's manifest entry based on a false premise: a stale tsconfig.build.tsbuildinfo (gitignored, untouched by git checkout/rebase) told tsc the file's compilation was already current even though git's own checkout had deleted the actual output file during this branch's rebase onto #4488's merge (a tracked-in-old-tree, untracked-in-new-tree transition deletes the working-tree file regardless of the new .gitignore entry). tsc's incremental cache doesn't verify its recorded output still exists on disk, so it silently skipped re-emitting it. Confirmed real root cause: deleting tsconfig.build.tsbuildinfo and rebuilding fresh correctly re-emits gsd-core/bin/lib/tdd-red-evidence.cjs (it is gitignored now, not deleted -- src/tdd-red-evidence.cts is unaffected by ADR-457's tracked-vs-gitignored distinction and always compiles). The manifest's own purpose (per its docstring) is 'every shipped surface derived entirely from the filesystem' -- this file still ships via the normal build, so it belongs in the manifest regardless of git-tracking status. Net result matches next's own INVENTORY-MANIFEST.json byte-for-byte; this correction should not have been needed at all had the build cache been fresh when the prior commit was made. Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: sim Co-authored-by: Claude Sonnet 5 --- tests/capability-registry.test.cjs | 41 +++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/capability-registry.test.cjs b/tests/capability-registry.test.cjs index 8d806420a..37b9bd998 100644 --- a/tests/capability-registry.test.cjs +++ b/tests/capability-registry.test.cjs @@ -6286,7 +6286,11 @@ const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows'); * between the ```bash / ```sh / ```shell fence markers. */ function extractShellBlocks(content) { - const allLines = content.split('\n'); + // #4489: CRLF-safe split (mirrors src/text-lines.cts's splitLines() and the + // fixed sibling copy in tests/runtime-launcher-parity.test.cjs:221) — a bare + // '\n' split leaves a trailing \r on every line on a CRLF checkout, which + // reaches lineHasBareGsdTools' whitespace tokenizer below. + const allLines = content.split(/\r?\n/); const blocks = []; let inBlock = false; let blockLang = null; @@ -6369,6 +6373,41 @@ function lineHasBareGsdTools(line) { const AGENTS_DIR = path.join(__dirname, '..', 'agents'); +describe('bug #4489: extractShellBlocks is CRLF-safe (sibling of #4409)', () => { + test('a CRLF-line-ending fenced block yields lines with no trailing \\r', () => { + const content = [ + '# doc', + '', + '```bash', + 'echo one', + 'echo two', + '```', + '', + ].join('\r\n'); + const blocks = extractShellBlocks(content); + assert.strictEqual(blocks.length, 1, 'expected exactly one extracted block'); + assert.deepStrictEqual(blocks[0].lines, ['echo one', 'echo two']); + for (const line of blocks[0].lines) { + assert.ok(!line.includes('\r'), `line carried a trailing/embedded \\r: ${JSON.stringify(line)}`); + } + }); + + test('LF-only input is unaffected (pre-existing behavior unchanged)', () => { + const content = [ + '# doc', + '', + '```sh', + 'echo one', + 'echo two', + '```', + '', + ].join('\n'); + const blocks = extractShellBlocks(content); + assert.strictEqual(blocks.length, 1); + assert.deepStrictEqual(blocks[0].lines, ['echo one', 'echo two']); + }); +}); + describe('bug #1041: agent files must not call bare gsd-tools (all-runtime resolver)', () => { test('no agents/gsd-*.md file contains a bare gsd-tools command', () => { const files = fs.readdirSync(AGENTS_DIR).filter((f) => f.startsWith('gsd-') && f.endsWith('.md'));