fix(ab-corpus): preserve markdown structure across kept @domain blocks

Codex stop-time review caught that the Phase 2 domain filter
corrupted output when two adjacent kept blocks abutted: the regex's
trailing `\s*` consumed every character of whitespace BEFORE the next
match's leading `\s*`, so block N's body ended on its closing ``` and
block N+1's body started on its `### heading` with no separator at
all. Output: ` ```### Audit / activity feed` on a single line — fence
left unclosed, heading swallowed.

Replace the greedy `\s*` on both sides of each marker with a literal
`\n`, so the regex only consumes the single newline immediately
adjoining the comment. Surrounding blank lines stay in the
surrounding text where they belong, separating adjacent kept blocks
naturally.

Adds a regression guard test that searches the output for ` ```###`
(closing fence directly followed by a level-3 heading on the same
line). Codex effectively asked for it.

3767 vitest pass, format clean. Side effect: dropped blocks now
leave their surrounding blank lines intact, so dropped-block savings
shrink by ~3-5 chars per block; total Phase 2 savings stay within
the 500-char floor the existing test asserts.
This commit is contained in:
Fini 2026-04-29 09:49:50 +08:00
parent c835976479
commit f0a575e210
2 changed files with 29 additions and 2 deletions

View file

@ -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(/```###/);
}
}
});
});

View file

@ -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):
*
* <!-- @domain:mobile -->
* ... 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(
/<!-- @domain:([a-z,\s]+) -->\s*([\s\S]*?)\s*<!-- \/@domain -->\s*/g,
/<!-- @domain:([a-z,\s]+) -->\n([\s\S]*?)\n<!-- \/@domain -->/g,
(_, tags: string, body: string) => {
const allowed = tags.split(',').map((t) => t.trim());
return allowed.includes(keep) ? body : '';