From 0694dcf44090d55708b93050a6b49e219cbc815f Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Wed, 24 Jun 2026 22:27:31 -0400 Subject: [PATCH] fix(skills): make media-use frontmatter valid YAML so `skills add` works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `media-use` SKILL.md `description:` was an unquoted YAML scalar containing a mid-value `: ` (`...the full cascade: project cache...`). YAML 1.2 reads that as a nested mapping and the parse fails with "Nested mappings are not allowed in compact mappings". `skills add` aborts the entire install when any one skill fails to parse, so this single file blocked installing all 19 skills for everyone following the README's `npx skills add heygen-com/hyperframes`. - Replace the offending `: ` with ` — ` (keeps the plain-scalar style used by the other 18 skills; the description already uses `—` as a separator). - Add a frontmatter guard to scripts/lint-skills.ts that flags unquoted top-level scalars containing `: ` — the exact ambiguity — so this can't regress. No new dependency. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/lint-skills.ts | 44 ++++++++++++++++++++++++++++++++++++++- skills/media-use/SKILL.md | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/scripts/lint-skills.ts b/scripts/lint-skills.ts index 724ace5426..55b046fc5f 100644 --- a/scripts/lint-skills.ts +++ b/scripts/lint-skills.ts @@ -56,6 +56,43 @@ function collectSkillFiles(dir: string): string[] { return files; } +/** + * Flag YAML frontmatter that won't parse, which aborts `skills add` for the + * WHOLE repo (one bad SKILL.md blocks installing every skill). + * + * ponytail: targets the one failure mode we've actually hit — an unquoted + * top-level scalar whose value contains `: ` (colon-space), which YAML 1.2 + * reads as a nested mapping ("Nested mappings are not allowed in compact + * mappings"). Not a full YAML parse; if a different malformation appears, + * swap this for a real parser (the `yaml` package). + */ +function lintFrontmatter(content: string): Omit[] { + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) return []; + const violations: Omit[] = []; + const fmLines = match[1].split("\n"); + for (let i = 0; i < fmLines.length; i++) { + const line = fmLines[i]; + // Top-level `key: value` (no indentation). Skip block scalars (> |), + // already-quoted values, and flow collections — those handle colons fine. + const m = line.match(/^([A-Za-z0-9_-]+):[ \t]+(.+)$/); + if (!m) continue; + const value = m[2].trim(); + if (/^["'>|[{]/.test(value)) continue; + if (/:[ \t]/.test(value)) { + violations.push({ + line: i + 2, // +1 for the opening `---`, +1 for 1-based + message: + `Unquoted frontmatter value for "${m[1]}" contains ": " — YAML reads ` + + `this as a nested mapping and the parse fails, which aborts ` + + `\`skills add\` for the entire repo. Quote the value or rephrase the colon.`, + text: line.trim(), + }); + } + } + return violations; +} + /** Strip fenced code blocks so we only lint prose + inline code. */ function stripFencedBlocks(content: string): string { return content.replace(/^```[\s\S]*?^```/gm, (match) => @@ -68,9 +105,14 @@ function stripFencedBlocks(content: string): string { function lintFile(filePath: string): Violation[] { const raw = readFileSync(filePath, "utf-8"); + const file = relative(process.cwd(), filePath); + const violations: Violation[] = lintFrontmatter(raw).map((v) => ({ + ...v, + file, + })); + const stripped = stripFencedBlocks(raw); const lines = stripped.split("\n"); - const violations: Violation[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; diff --git a/skills/media-use/SKILL.md b/skills/media-use/SKILL.md index cbe99b8ea6..100b0f2240 100644 --- a/skills/media-use/SKILL.md +++ b/skills/media-use/SKILL.md @@ -1,6 +1,6 @@ --- name: media-use -description: Agent Media OS — resolve any media need (BGM, SFX, image, icon) into a frozen local file + ledger record. One verb (`resolve`) handles the full cascade: project cache, global cache, HeyGen catalog search, freeze, register. Keeps search noise on disk, hands the agent a path. Use when a composition needs background music, sound effects, images, or icons. +description: Agent Media OS — resolve any media need (BGM, SFX, image, icon) into a frozen local file + ledger record. One verb (`resolve`) handles the full cascade — project cache, global cache, HeyGen catalog search, freeze, register. Keeps search noise on disk, hands the agent a path. Use when a composition needs background music, sound effects, images, or icons. --- # media-use