mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
fix(lint): only unwrap whole-page code fences, not mid-document blocks (takeover of #1417)
Detection used /m regexes (a fence anywhere) while the fixer used string-anchored regexes, so a note that merely CONTAINS a ```markdown block lost only its closing fence and was re-corrupted every autopilot lint cycle. Detection and fix now share a single analyzeMarkdownWrap check: a page is wrapped only when the entire body (after frontmatter) is one fence. Salvaged from PR #1417 with the unrelated plugin.json + perplexity-research commits dropped (off-mission, reference a private external system). Co-authored-by: trinh-macbook <trinh-macbook@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
trinh-macbook
Claude Fable 5
parent
ed1563983f
commit
393369090b
+51
-5
@@ -68,6 +68,49 @@ const LLM_PREAMBLES = [
|
||||
/^Absolutely\.?\s*Here[^.\n]*\.?\s*\n*/gim,
|
||||
];
|
||||
|
||||
// ── Whole-page code-fence wrap (LLM artifact) ───────────────────────
|
||||
//
|
||||
// An LLM sometimes returns an ENTIRE page wrapped in a ```markdown fence.
|
||||
// Strip that wrap — but ONLY when the whole body is wrapped, never when a note
|
||||
// merely *contains* a ```markdown block mid-document (e.g. a Notion export that
|
||||
// fences a config snippet). The earlier code detected the wrap with /m regexes
|
||||
// (a fence anywhere) but stripped the closing fence with a string-anchored
|
||||
// regex, so a mid-document block lost only its trailing ``` and was left with
|
||||
// an open fence — re-corrupted on every autopilot lint cycle. Detection and fix
|
||||
// now share this single check so they can't drift apart again.
|
||||
|
||||
function frontmatterLength(content: string): number {
|
||||
const m = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/.exec(content);
|
||||
return m ? m[0].length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat a page as fence-wrapped only when the entire body (after any YAML
|
||||
* frontmatter) is a single ```markdown fence: the first non-blank body line
|
||||
* opens it, the last non-blank body line closes it, and no bare ``` appears
|
||||
* between them (which would mean the fence closed early — not a whole-page
|
||||
* wrap). Returns the unwrapped content (frontmatter preserved) when wrapped.
|
||||
*/
|
||||
function analyzeMarkdownWrap(content: string): { wrapped: boolean; unwrapped: string } {
|
||||
const fmLen = frontmatterLength(content);
|
||||
const head = content.slice(0, fmLen);
|
||||
const lines = content.slice(fmLen).split('\n');
|
||||
|
||||
let first = 0;
|
||||
while (first < lines.length && lines[first].trim() === '') first++;
|
||||
let last = lines.length - 1;
|
||||
while (last >= 0 && lines[last].trim() === '') last--;
|
||||
|
||||
if (first >= last) return { wrapped: false, unwrapped: content };
|
||||
if (!/^```(?:markdown|md)\s*$/.test(lines[first].trim())) return { wrapped: false, unwrapped: content };
|
||||
if (lines[last].trim() !== '```') return { wrapped: false, unwrapped: content };
|
||||
for (let i = first + 1; i < last; i++) {
|
||||
if (lines[i].trim().startsWith('```')) return { wrapped: false, unwrapped: content };
|
||||
}
|
||||
|
||||
return { wrapped: true, unwrapped: head + lines.slice(first + 1, last).join('\n') };
|
||||
}
|
||||
|
||||
// ── Rules ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -126,8 +169,10 @@ export function lintContent(content: string, filePath: string, opts: LintContent
|
||||
}
|
||||
}
|
||||
|
||||
// Rule: Wrapping code fences (```markdown ... ```)
|
||||
if (content.match(/^```(?:markdown|md)\s*\n/m) && content.match(/\n```\s*$/m)) {
|
||||
// Rule: Wrapping code fences — the WHOLE page body wrapped in ```markdown …
|
||||
// ``` (an LLM artifact). A ```markdown block mid-document is legitimate and
|
||||
// must NOT trigger this (see analyzeMarkdownWrap).
|
||||
if (analyzeMarkdownWrap(content).wrapped) {
|
||||
issues.push({
|
||||
file: filePath, line: 1, rule: 'code-fence-wrap',
|
||||
message: 'Page wrapped in ```markdown code fences (LLM artifact)',
|
||||
@@ -291,9 +336,10 @@ export function fixContent(content: string): string {
|
||||
fixed = fixed.replace(pattern, '');
|
||||
}
|
||||
|
||||
// Fix wrapping code fences
|
||||
fixed = fixed.replace(/^```(?:markdown|md)\s*\n/, '');
|
||||
fixed = fixed.replace(/\n```\s*$/, '');
|
||||
// Fix wrapping code fences — only a genuine whole-page wrap (see
|
||||
// analyzeMarkdownWrap); never strip the closing fence of a mid-note block.
|
||||
const wrap = analyzeMarkdownWrap(fixed);
|
||||
if (wrap.wrapped) fixed = wrap.unwrapped;
|
||||
|
||||
// Clean up excessive blank lines left by fixes
|
||||
fixed = fixed.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
@@ -32,6 +32,14 @@ describe('lintContent', () => {
|
||||
expect(issues.some(i => i.rule === 'code-fence-wrap')).toBe(true);
|
||||
});
|
||||
|
||||
test('does not flag a mid-document markdown code block as a page wrap', () => {
|
||||
// Regression: a note that merely CONTAINS a ```markdown block (e.g. a
|
||||
// Notion export fencing a config snippet) is not a whole-page wrap.
|
||||
const content = '---\ntitle: Notes\ntype: note\ncreated: 2026-05-25\n---\n\nIntro paragraph.\n\n```markdown\nKEY=value\nPORT=3002\n```\n';
|
||||
const issues = lintContent(content, 'test.md');
|
||||
expect(issues.some(i => i.rule === 'code-fence-wrap')).toBe(false);
|
||||
});
|
||||
|
||||
test('detects placeholder dates', () => {
|
||||
const content = '---\ntitle: Test\ntype: person\ncreated: YYYY-MM-DD\n---\n\n# Test';
|
||||
const issues = lintContent(content, 'test.md');
|
||||
@@ -97,6 +105,17 @@ describe('fixContent', () => {
|
||||
expect(fixed).toContain('# Title');
|
||||
});
|
||||
|
||||
test('leaves a mid-document markdown code block intact (keeps its closing fence)', () => {
|
||||
// Regression for the autopilot churn: fixContent used to strip only the
|
||||
// trailing ```, leaving the fence open and the note re-corrupted each cycle.
|
||||
const input = '---\ntitle: Notes\ntype: note\ncreated: 2026-05-25\n---\n\nIntro paragraph.\n\n```markdown\nKEY=value\nPORT=3002\n```\n';
|
||||
const fixed = fixContent(input);
|
||||
const fenceLines = (fixed.match(/^```/gm) ?? []).length;
|
||||
expect(fenceLines).toBe(2);
|
||||
expect(fixed).toContain('```markdown');
|
||||
expect(fixed.trimEnd().endsWith('```')).toBe(true);
|
||||
});
|
||||
|
||||
test('cleans up excessive blank lines after fix', () => {
|
||||
const input = 'Of course. Here is the brain page.\n\n\n\n# Title\n\nContent.';
|
||||
const fixed = fixContent(input);
|
||||
|
||||
Reference in New Issue
Block a user