From e03a9edd446a0b182a5ea09092c4bb7fae895e2f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 23 Mar 2026 22:11:48 -0400 Subject: [PATCH] fix: replace invalid \Z regex anchor and remove redundant pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original PR (#1337) used \Z in a JavaScript regex, which is a Perl/Python/Ruby anchor — JavaScript interprets it as a literal match for the character 'Z', silently truncating expected text containing that letter. Replace with a two-pass approach: try next-key lookahead first, fall back to greedy match to end-of-string. Also remove the redundant `to=all:` pattern in sanitizeForDisplay() since it is a subset of the existing `to=[^:\s]+:` pattern. Add regression tests proving the Z-truncation bug and verifying expected blocks at end-of-section parse correctly. Co-Authored-By: Claude Opus 4.6 (1M context) --- get-shit-done/bin/lib/security.cjs | 1 - get-shit-done/bin/lib/uat.cjs | 3 +- tests/uat.test.cjs | 45 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/get-shit-done/bin/lib/security.cjs b/get-shit-done/bin/lib/security.cjs index f64a5642a..ffef1fc51 100644 --- a/get-shit-done/bin/lib/security.cjs +++ b/get-shit-done/bin/lib/security.cjs @@ -237,7 +237,6 @@ function sanitizeForDisplay(text) { const protocolLeakPatterns = [ /^\s*(?:assistant|user|system)\s+to=[^:\s]+:[^\n]+$/i, - /^\s*(?:assistant|user|system)\s+to=all:[^\n]+$/i, /^\s*<\|(?:assistant|user|system)[^|]*\|>\s*$/i, ]; diff --git a/get-shit-done/bin/lib/uat.cjs b/get-shit-done/bin/lib/uat.cjs index d34a4b683..7a63495e9 100644 --- a/get-shit-done/bin/lib/uat.cjs +++ b/get-shit-done/bin/lib/uat.cjs @@ -135,7 +135,8 @@ function parseCurrentTest(content) { const numberMatch = section.match(/^number:\s*(\d+)\s*$/m); const nameMatch = section.match(/^name:\s*(.+)\s*$/m); - const expectedBlockMatch = section.match(/^expected:\s*\|\n([\s\S]*?)(?=^\w[\w-]*:\s|\Z)/m); + const expectedBlockMatch = section.match(/^expected:\s*\|\n([\s\S]*?)(?=^\w[\w-]*:\s)/m) + || section.match(/^expected:\s*\|\n([\s\S]+)/m); const expectedInlineMatch = section.match(/^expected:\s*(.+)\s*$/m); if (!numberMatch || !nameMatch || (!expectedBlockMatch && !expectedInlineMatch)) { diff --git a/tests/uat.test.cjs b/tests/uat.test.cjs index f708afb0f..40aaec245 100644 --- a/tests/uat.test.cjs +++ b/tests/uat.test.cjs @@ -390,6 +390,51 @@ awaiting: user response assert.ok(result.output.includes('Chinese strings render correctly.')); }); + test('does not truncate expected text containing the letter Z', () => { + fs.writeFileSync(uatPath, `--- +status: testing +phase: 01-test-phase +--- + +## Current Test + +number: 3 +name: Timezone display +expected: | + Timezone abbreviation shows CET. + Zero-offset zones display correctly. +awaiting: user response +`); + + const result = runGsdTools(['uat', 'render-checkpoint', '--file', '.planning/phases/01-test-phase/01-UAT.md', '--raw'], tmpDir); + assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`); + assert.ok(result.output.includes('Timezone abbreviation shows CET.'), + 'Expected text before Z-containing word should be present'); + assert.ok(result.output.includes('Zero-offset zones display correctly.'), + 'Expected text starting with Z should not be truncated by \\Z regex bug'); + }); + + test('parses expected block when it is the last field in the section', () => { + fs.writeFileSync(uatPath, `--- +status: testing +phase: 01-test-phase +--- + +## Current Test + +number: 4 +name: Final field test +expected: | + This block has no trailing YAML key. + It ends at the section boundary. +`); + + const result = runGsdTools(['uat', 'render-checkpoint', '--file', '.planning/phases/01-test-phase/01-UAT.md', '--raw'], tmpDir); + assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`); + assert.ok(result.output.includes('This block has no trailing YAML key.')); + assert.ok(result.output.includes('It ends at the section boundary.')); + }); + test('fails when testing is already complete', () => { fs.writeFileSync(uatPath, `--- status: complete