fix(skills): make media-use frontmatter valid YAML so skills add works (#1709)

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) <noreply@anthropic.com>
This commit is contained in:
Miguel Ángel
2026-06-24 22:33:28 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 34bb496566
commit 814c96cefa
2 changed files with 44 additions and 2 deletions
+43 -1
View File
@@ -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<Violation, "file">[] {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) return [];
const violations: Omit<Violation, "file">[] = [];
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];
+1 -1
View File
@@ -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 cascadeproject 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