diff --git a/src/core/skill-brain-first.ts b/src/core/skill-brain-first.ts index 321b8a117..9a666ff98 100644 --- a/src/core/skill-brain-first.ts +++ b/src/core/skill-brain-first.ts @@ -162,9 +162,11 @@ export const PHASE_HEADING_RE = /^##+\s*(?:Phase\s*1|Step\s*0)\b[^\n]*brain/im; /** * Frontmatter fence regex used by body extraction. Conservative match: * leading `---\n` through the next `\n---` (greedy stop). Matches the - * shape `parseSkillFrontmatter` already accepts. + * shape `parseSkillFrontmatter` already accepts, including CRLF fences — + * if this stayed LF-only, a CRLF skill's `tools: [web_search]` frontmatter + * would survive into the body scan and false-flag the skill (F6). */ -const FRONTMATTER_RE = /^---\n[\s\S]*?\n---\n?/; +const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/; // --------------------------------------------------------------------------- // Hardcoded EXEMPT_SKILLS (CMT1 — replaces the dropped upgrade migration) diff --git a/src/core/skill-manifest.ts b/src/core/skill-manifest.ts index 8df681c59..c3766f45f 100644 --- a/src/core/skill-manifest.ts +++ b/src/core/skill-manifest.ts @@ -47,7 +47,7 @@ export interface ManifestLoadResult { function parseSkillName(skillMdPath: string): string | null { try { const content = readFileSync(skillMdPath, 'utf-8'); - const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); + const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!fmMatch) return null; const fm = fmMatch[1]; // Match `name: foo` or `name: "foo"` or `name: 'foo'` diff --git a/test/skill-brain-first.test.ts b/test/skill-brain-first.test.ts index 853050c31..34530e3e4 100644 --- a/test/skill-brain-first.test.ts +++ b/test/skill-brain-first.test.ts @@ -227,6 +227,20 @@ describe('stripFrontmatter', () => { // Body should NOT contain `web_search` (it was in the stripped frontmatter). expect(body.includes('web_search')).toBe(false); }); + + test('F6 holds for CRLF fences — frontmatter tools do not leak into the body scan', () => { + const content = [ + '---', + 'name: x', + 'tools: [web_search]', + '---', + '', + '# x', + 'Body says gbrain search comes first.', + ].join('\r\n'); + const body = stripFrontmatter(content); + expect(body.includes('web_search')).toBe(false); + }); }); describe('offset helpers', () => {