{"record":{"id":"d1dd1f707e598765","repo":"DietrichGebert/ponytail","slug":"description-for-name-must-be-one-line-no-quote","errorCode":null,"errorMessage":"description for ${name} must be one line, no quotes, under 160 chars","messagePattern":"description for (.+?) must be one line, no quotes, under 160 chars","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/build-openclaw-skills.js","lineNumber":40,"sourceCode":"  '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 };\n\nif (require.main === module) {\n  for (const name of NAMES) {\n    const p = outPath(name);\n    fs.mkdirSync(path.dirname(p), { recursive: true });\n    fs.writeFileSync(p, render(name));\n    console.log('wrote', path.relative(ROOT, p).replace(/\\\\/g, '/'));","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/DietrichGebert/ponytail/blob/2ed6c52c9d7e5e56942508591085fd45dea277d3/scripts/build-openclaw-skills.js#L22-L58","documentation":"Thrown by render() in scripts/build-openclaw-skills.js. Before embedding a description into the canonical frontmatter, render() validates the DESCRIPTIONS[name] string: length must be <= 160 chars, it must contain no newline (single line), and it must contain no double-quote character (because the description is emitted wrapped in double quotes as description: \"${desc}\"). Any violation throws, since a malformed description would produce broken YAML frontmatter in the generated .openclaw skill bundle.","triggerScenarios":"Editing the DESCRIPTIONS object in scripts/build-openclaw-skills.js and adding/altering an entry whose value exceeds 160 characters, spans multiple lines (contains '\\n'), or contains a '\"' character. Then running node scripts/build-openclaw-skills.js, which iterates NAMES and calls render() on each.","commonSituations":"A new ponytail skill is added with a verbose one-liner that crosses the 160-char openclaw/skill-registry limit. A description is copy-pasted from prose containing a smart/curly double quote or an actual ASCII double quote. A contributor formats a description across two lines for readability in the source, introducing a literal newline.","solutions":["Shorten the offending DESCRIPTIONS[name] value to <= 160 characters on a single line with no double quotes.","Replace any double quotes in the description with single quotes or backticks; remove literal newlines.","Re-run node scripts/build-openclaw-skills.js and confirm render() passes for that skill."],"exampleFix":"// before — scripts/build-openclaw-skills.js\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. \"Quick\" one-shot report.',\n\n// after — single line, <= 160 chars, no double quotes\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. One-shot report.',","handlingStrategy":"validation","validationCode":"// Validate the whole DESCRIPTIONS map at module load, before build runs\nfunction validDescription(d) {\n  return typeof d === 'string' && d.length <= 160 && !d.includes('\\n') && !d.includes('\"');\n}\nfor (const [name, desc] of Object.entries(DESCRIPTIONS)) {\n  if (!validDescription(desc)) {\n    throw new Error(`${name}: description must be one line, no double quotes, <= 160 chars (got ${desc.length})`);\n  }\n}","typeGuard":"function isValidDescription(d) {\n  return typeof d === 'string' && d.length > 0 && d.length <= 160 && !d.includes('\\n') && !d.includes('\"');\n}","tryCatchPattern":"try {\n  render(name);\n} catch (e) {\n  if (/must be one line/.test(e.message)) {\n    console.error(`Fix DESCRIPTIONS['${name}']: <= 160 chars, single line, no double quotes.`);\n  }\n  throw e;\n}","preventionTips":["Enforce the 160-char / single-line / no-quote rule with an editor lint or a unit test on the DESCRIPTIONS map.","When adding a skill, write the description first and run render(name) in isolation before committing.","Avoid pasting prose that may carry smart/curly double quotes; retype quotes as ASCII single quotes."],"tags":["validation","build","config","strings","yaml"],"backgroundTag":null,"analyzedSha":"2ed6c52c9d7e5e56942508591085fd45dea277d3","analyzedAt":"2026-08-12T23:02:21.847Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}