// Minimal YAML-subset frontmatter parser for upstream SKILL.md files. // Supports exactly what mattpocock/skills uses (census 2026-10-14, @49dd158): top-level // `key: value` scalars, double/single-quoted strings with \" escapes, and nested blocks // (e.g. `metadata:`) which are skipped. test/generate.test.ts asserts every vendored skill // parses with non-empty name+description, so an upstream format change fails the build // instead of silently producing a broken manifest. export type Frontmatter = Record export function parseFrontmatter(content: string): { frontmatter: Frontmatter; body: string } { const text = content.replace(/^\uFEFF/, "") if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) { return { frontmatter: {}, body: text } } const end = text.indexOf("\n---", 3) if (end === -1) return { frontmatter: {}, body: text } const raw = text.slice(text.indexOf("\n") + 1, end + 1) const body = text.slice(text.indexOf("\n", end + 1) + 1) const frontmatter: Frontmatter = {} for (const line of raw.split(/\r?\n/)) { if (!line || /^\s/.test(line) || line.trim() === "---") continue // nested block or separator const m = /^([A-Za-z][\w-]*):\s*(.*)$/.exec(line) if (!m) continue frontmatter[m[1]] = unquote(m[2].trim()) } return { frontmatter, body } } function unquote(value: string): string { if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) { return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\") } if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) { return value.slice(1, -1).replace(/''/g, "'") } return value } export function isUserInvoked(frontmatter: Frontmatter): boolean { return frontmatter["disable-model-invocation"] === "true" }