Normalize semantic layer descriptions

This commit is contained in:
Luca Martial 2026-05-11 00:31:15 -07:00
parent c82989119b
commit 86c818a454
21 changed files with 498 additions and 37 deletions

View file

@ -37,6 +37,42 @@ describe('WikiWriteTool', () => {
expect(result.markdown).toMatch(/created/i);
});
it('normalizes accidentally escaped markdown newlines before writing', async () => {
const { tool, wikiService } = makeTool();
await tool.call(
{
key: 'large-contract-requesters',
summary: 'Cross-schema Metabase query',
content:
'# Large Contract Requesters\\n\\n**Source card:** Metabase #110\\n\\n## SQL\\n\\n```sql\\nselect * from orbit_analytics.mart_account_segments\\n```\\n',
} as any,
baseContext,
);
expect(wikiService.writePage.mock.calls[0][4]).toBe(
'# Large Contract Requesters\n\n**Source card:** Metabase #110\n\n## SQL\n\n```sql\nselect * from orbit_analytics.mart_account_segments\n```\n',
);
expect(wikiService.syncSinglePage.mock.calls[0][4]).toBe(
'# Large Contract Requesters\n\n**Source card:** Metabase #110\n\n## SQL\n\n```sql\nselect * from orbit_analytics.mart_account_segments\n```\n',
);
});
it('preserves intentional escaped newline examples in inline code', async () => {
const { tool, wikiService } = makeTool();
await tool.call(
{
key: 'newline-token',
summary: 'Escaped newline token',
content: 'Use `\\n\\n` when documenting the literal separator.',
} as any,
baseContext,
);
expect(wikiService.writePage.mock.calls[0][4]).toBe('Use `\\n\\n` when documenting the literal separator.');
});
it('skips syncSinglePage when session is worktree-scoped', async () => {
const { tool, wikiService } = makeTool();
const session: ToolSession = {

View file

@ -47,6 +47,22 @@ interface WikiWriteStructured {
action?: 'created' | 'updated';
}
function looksLikeEscapedMarkdown(content: string): boolean {
const withoutInlineCode = content.replace(/`[^`]*`/g, '');
return /\\n\\n|(?:^|\\n)#{1,6}\s|\\n[-*]\s|\\n\d+\.\s|\\n```|\\n\|/.test(withoutInlineCode);
}
function normalizeAccidentalEscapedMarkdownNewlines(content: string): string {
const escapedBreaks = content.match(/\\[rn]/g)?.length ?? 0;
if (escapedBreaks < 2) return content;
const actualBreaks = content.match(/\r?\n/g)?.length ?? 0;
if (actualBreaks > 0 && escapedBreaks <= actualBreaks * 4) return content;
if (!looksLikeEscapedMarkdown(content)) return content;
return content.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\\r/g, '\n');
}
export class WikiWriteTool extends BaseTool<typeof wikiWriteInputSchema> {
readonly name = 'wiki_write';
@ -125,7 +141,7 @@ tags/refs/sl_refs use REPLACE semantics: omit to keep existing on update, [] to
};
if (input.content) {
finalContent = input.content;
finalContent = normalizeAccidentalEscapedMarkdownNewlines(input.content);
} else {
const editResult = applySqlEdits(existing?.content ?? '', input.replacements ?? []);
if (!editResult.success) {