fix: replace invalid \Z regex anchor and remove redundant pattern

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) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-03-23 22:11:48 -04:00
parent 763c6cc642
commit e03a9edd44
3 changed files with 47 additions and 2 deletions

View File

@@ -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,
];

View File

@@ -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)) {

View File

@@ -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