diff --git a/src/commands/lint.ts b/src/commands/lint.ts index e14e05456..54b81c290 100644 --- a/src/commands/lint.ts +++ b/src/commands/lint.ts @@ -127,7 +127,12 @@ 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)) { + // Detector intentionally has NO /m flag so ^/$ match start/end of the whole + // file, not inner lines. Keeps detector in sync with fixContent() below, + // which also has no /m flag. Without this, lint reports "fixable" false + // positives on any page that simply contains a ```markdown code block, but + // fixContent can never strip them (its regex only matches whole-file wrappers). + if (content.match(/^```(?:markdown|md)\s*\n/) && content.match(/\n```\s*$/)) { issues.push({ file: filePath, line: 1, rule: 'code-fence-wrap', message: 'Page wrapped in ```markdown code fences (LLM artifact)', diff --git a/test/lint.test.ts b/test/lint.test.ts index ca38b8d23..f843d5859 100644 --- a/test/lint.test.ts +++ b/test/lint.test.ts @@ -32,6 +32,29 @@ describe('lintContent', () => { expect(issues.some(i => i.rule === 'code-fence-wrap')).toBe(true); }); + test('no false positive: page CONTAINS an inner ```markdown code block', () => { + // Real-world case: a docs/SKILL page that shows a markdown example inline. + // Before this fix, the detector used the /m flag so ^/$ matched start/end + // of any line, which fired on any file that simply contained a ```markdown + // line. But fixContent's regex has no /m flag and can only strip whole-file + // wrappers, so the issue was reported as "fixable: true" yet never fixed. + const content = + '---\ntitle: Skill\n---\n\n# Skill\n\nExample input shape:\n\n' + + '```markdown\n# Inner page\nContent.\n```\n\nThat ends the example.\n'; + const issues = lintContent(content, 'test.md'); + expect(issues.filter(i => i.rule === 'code-fence-wrap')).toHaveLength(0); + }); + + test('no false positive: multiple inner ```markdown blocks', () => { + // Documentation pages frequently include several markdown examples. + const content = + '---\ntitle: Examples\n---\n\n# Examples\n\nFirst:\n\n' + + '```markdown\nfoo\n```\n\nSecond:\n\n' + + '```markdown\nbar\n```\n\nDone.\n'; + const issues = lintContent(content, 'test.md'); + expect(issues.filter(i => i.rule === 'code-fence-wrap')).toHaveLength(0); + }); + test('detects placeholder dates', () => { const content = '---\ntitle: Test\ntype: person\ncreated: YYYY-MM-DD\n---\n\n# Test'; const issues = lintContent(content, 'test.md');