diff --git a/scripts/ab-corpus/__tests__/build-prompt.test.ts b/scripts/ab-corpus/__tests__/build-prompt.test.ts index f6171648f..1c1ffcfa0 100644 --- a/scripts/ab-corpus/__tests__/build-prompt.test.ts +++ b/scripts/ab-corpus/__tests__/build-prompt.test.ts @@ -176,4 +176,24 @@ describe('buildSystemPrompt', () => { expect(full - filtered).toBeGreaterThan(500); } }); + + it('T + category preserves Markdown structure across adjacent kept blocks (no fence-heading collision)', () => { + // Earlier regex consumed surrounding whitespace too greedily, joining + // body1's closing ``` directly to body2's heading like ```### Audit... + // and breaking both the code fence and the heading rendering. Codex + // stop-time review caught it. Guard against re-introducing the + // collision by asserting fence-followed-by-heading never appears + // anywhere in the output for any (category, difficulty) combo where + // the filter could fire. + for (const category of ['mobile', 'dashboard', 'landing'] as const) { + for (const difficulty of ['obvious', 'composite'] as const) { + const built = buildSystemPrompt('T', { difficulty, category }); + // Closing fence directly followed by a level-3 heading on the + // same line is the corruption signature: ``` and ### must be + // separated by at least a newline. The buggy regex produced + // `\`\`\`### Audit ...` with no separator at all. + expect(built.system).not.toMatch(/```###/); + } + } + }); }); diff --git a/scripts/ab-corpus/build-prompt.ts b/scripts/ab-corpus/build-prompt.ts index 2a8a037f8..e720fd5b5 100644 --- a/scripts/ab-corpus/build-prompt.ts +++ b/scripts/ab-corpus/build-prompt.ts @@ -108,7 +108,7 @@ export interface BuildPromptOpts { * multi-file splits to maintain per domain) and keeps the diff * surgical — adding a tag is two HTML comments, no structural move. * - * Block syntax: + * Block syntax (each marker on its own line): * * * ... mobile-only content ... @@ -118,10 +118,17 @@ export interface BuildPromptOpts { * and a block matches if `keep` appears in the list. This is the * common case for tools like calendar_grid that legitimately span * domains. + * + * Whitespace contract: the regex consumes ONLY the single newlines + * adjoining each marker, not surrounding blank lines. That preserves + * the blank-line separator between adjacent kept blocks — without + * this, the closing ` ``` ` of one recipe would butt up against the + * `### ` heading of the next, breaking markdown structure (caught + * by Codex stop-time review). */ function stripNonMatchingDomains(text: string, keep: string): string { return text.replace( - /\s*([\s\S]*?)\s*\s*/g, + /\n([\s\S]*?)\n/g, (_, tags: string, body: string) => { const allowed = tags.split(',').map((t) => t.trim()); return allowed.includes(keep) ? body : '';