{"record":{"id":"2876324fa83fcf71","repo":"DietrichGebert/ponytail","slug":"skills-name-skill-md-has-no-frontmatter","errorCode":null,"errorMessage":"skills/${name}/SKILL.md has no frontmatter","messagePattern":"skills/(.+?)/SKILL\\.md has no frontmatter","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-openclaw-skills.js","lineNumber":33,"sourceCode":"\nconst ROOT = path.join(__dirname, '..');\nconst HOMEPAGE = 'https://github.com/DietrichGebert/ponytail';\n\nconst DESCRIPTIONS = {\n  'ponytail': 'Lazy senior dev mode for any coding task (write, refactor, fix, review): YAGNI, stdlib first, no unrequested abstractions. Not for non-coding requests.',\n  'ponytail-review': 'Review a diff for over-engineering. Finds what to delete: reinvented stdlib, needless deps, speculative abstractions. One line per finding.',\n  'ponytail-audit': 'Audit the whole repo for over-engineering. A ranked list of what to delete, simplify, or replace with stdlib or native features.',\n  'ponytail-debt': 'Harvest every ponytail: shortcut comment into one debt ledger, so deferrals get tracked instead of forgotten. One-shot report.',\n  'ponytail-gain': 'Show ponytail measured impact as a scoreboard: less code, less cost, more speed, from the benchmark medians. One-shot display.',\n  'ponytail-help': \"Quick reference for ponytail's modes, skills, and commands. One-shot display.\",\n};\n\nconst NAMES = Object.keys(DESCRIPTIONS);\n\nfunction sourceBody(name) {\n  const src = fs.readFileSync(path.join(ROOT, 'skills', name, 'SKILL.md'), 'utf8').replace(/\\r\\n/g, '\\n');\n  const fm = src.match(/^---\\n[\\s\\S]*?\\n---\\n?/);\n  if (!fm) throw new Error(`skills/${name}/SKILL.md has no frontmatter`);\n  return src.slice(fm[0].length);\n}\n\nfunction render(name) {\n  const desc = DESCRIPTIONS[name];\n  if (desc.length > 160 || desc.includes('\\n') || desc.includes('\"')) {\n    throw new Error(`description for ${name} must be one line, no quotes, under 160 chars`);\n  }\n  const frontmatter =\n    `---\\nname: ${name}\\ndescription: \"${desc}\"\\nhomepage: ${HOMEPAGE}\\nlicense: MIT\\n---\\n`;\n  return frontmatter + sourceBody(name);\n}\n\nfunction outPath(name) {\n  return path.join(ROOT, '.openclaw', 'skills', name, 'SKILL.md');\n}\n\nmodule.exports = { DESCRIPTIONS, NAMES, render, outPath, sourceBody };","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/DietrichGebert/ponytail/blob/2ed6c52c9d7e5e56942508591085fd45dea277d3/scripts/build-openclaw-skills.js#L15-L51","documentation":"Thrown by sourceBody() in scripts/build-openclaw-skills.js when reading skills/${name}/SKILL.md. The function normalizes CRLF to LF, then runs /^---\\n[\\s\\S]*?\\n---\\n?/ to detect a YAML frontmatter block at the very start of the file. The build strips this frontmatter from each source skill and re-renders the file with canonical frontmatter (name/description/homepage/license), so every source skill MUST begin with a frontmatter block even though it is later discarded. If the regex finds no match the function throws, aborting the whole openclaw skill build.","triggerScenarios":"Running node scripts/build-openclaw-skills.js (or any npm script / CI step that imports it) when one of the skills listed in DESCRIPTIONS has a SKILL.md that: is empty, starts with body text or a markdown heading instead of '---', begins with leading whitespace/BOM before '---', or uses '--- ' (trailing space) / '---\\r' so it is not immediately followed by a plain newline. Also fires if a skill directory in DESCRIPTIONS has no SKILL.md at all (readFileSync throws ENOENT, but the no-frontmatter branch fires for present-yet-malformed files).","commonSituations":"A contributor copies a README-style markdown file into skills/<name>/SKILL.md and forgets frontmatter. A new skill is added to the DESCRIPTIONS map but its SKILL.md was never created or was left as a stub heading. A Windows editor saves the file with a UTF-8 BOM or CRLF, and the CRLF is normalized but a leading BOM is not stripped (the script only normalizes \\r\\n, not \\uFEFF). Someone renames a skill folder without updating the file path, leaving the old path's file without frontmatter.","solutions":["Read the error's ${name}, then open skills/${name}/SKILL.md and prepend a YAML frontmatter block that starts on the first byte: a line with exactly '---', one or more key lines, then a closing line with exactly '---'.","Confirm the file begins with '---' immediately followed by a newline (no leading spaces, no BOM). If a BOM is present, re-save the file as UTF-8 without BOM.","If the file is missing entirely, create skills/${name}/SKILL.md with a minimal frontmatter block ('---\\nplaceholder: true\\n---\\n') plus the skill body; the build will overwrite the frontmatter.","Re-run node scripts/build-openclaw-skills.js and confirm it progresses past this skill."],"exampleFix":"// before — skills/ponytail-review/SKILL.md (no frontmatter, starts with body)\n# Ponytail Review\nReview a diff for over-engineering...\n\n// after — prepend any frontmatter block (it is stripped and replaced at build)\n---\nplaceholder: true\n---\n# Ponytail Review\nReview a diff for over-engineering...","handlingStrategy":"validation","validationCode":"// Run before invoking sourceBody(name)\nconst fs = require('fs'), path = require('path');\nfunction hasFrontmatter(root, name) {\n  const src = fs.readFileSync(path.join(root, 'skills', name, 'SKILL.md'), 'utf8').replace(/\\r\\n/g, '\\n');\n  return /^---\\n[\\s\\S]*?\\n---\\n?/.test(src);\n}\nfor (const name of NAMES) {\n  if (!hasFrontmatter(ROOT, name)) {\n    throw new Error(`refusing to build: skills/${name}/SKILL.md lacks frontmatter`);\n  }\n}","typeGuard":"// Node has no runtime type to guard, but a structural check works:\nconst FRONTMATTER_RE = /^---\\n[\\s\\S]*?\\n---\\n?/;\nfunction hasValidFrontmatter(src) {\n  return typeof src === 'string' && FRONTMATTER_RE.test(src.replace(/\\r\\n/g, '\\n'));\n}","tryCatchPattern":"try {\n  sourceBody(name);\n} catch (e) {\n  if (String(e.message).includes('no frontmatter')) {\n    console.error(`skills/${name}/SKILL.md: prepend a YAML frontmatter block ('---\\\\n...\\\\n---\\\\n') at the very first byte`);\n  }\n  throw e;\n}","preventionTips":["Add a pre-commit/CI lint that asserts every skills/*/SKILL.md matches /^---\\n[\\s\\S]*?\\n---\\n?/ after CRLF normalization.","Also strip a leading \\uFEFF before the regex, since the script normalizes \\r\\n but not BOM.","Keep DESCRIPTIONS and the skills/*/ directories in sync — a rename in one must update the other in the same commit."],"tags":["build","markdown","yaml","frontmatter","validation"],"backgroundTag":null,"analyzedSha":"2ed6c52c9d7e5e56942508591085fd45dea277d3","analyzedAt":"2026-08-12T23:02:21.847Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}