feat: ship ponytail to OpenClaw (ClawHub skill package) (#102)
Adds .openclaw/skills/ (ponytail + review/audit/debt/help) generated from the canonical skills/ (verbatim body, no drift), a generator script, and a drift test. Verified live: loads as Ready in OpenClaw 2026.6.6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
caf138df56
commit
41d6c2f761
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
// Generate the OpenClaw / ClawHub skill package (.openclaw/skills/) from the
|
||||
// canonical skills/. OpenClaw skills are SKILL.md (frontmatter + body), the same
|
||||
// format ponytail already uses, with one difference: `description` must be a
|
||||
// single line under 160 chars. The canonical descriptions are long (tuned for
|
||||
// Claude's skill picker), so each ships a short one here. The body is copied
|
||||
// verbatim from skills/<name>/SKILL.md so the ruleset never drifts; only the
|
||||
// frontmatter is rewritten.
|
||||
//
|
||||
// Run: node scripts/build-openclaw-skills.js
|
||||
// tests/openclaw-skills.test.js fails if the committed copies are stale.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const HOMEPAGE = 'https://github.com/DietrichGebert/ponytail';
|
||||
|
||||
const DESCRIPTIONS = {
|
||||
'ponytail': 'Lazy senior dev mode. Forces the simplest, shortest solution that works: YAGNI, stdlib first, no unrequested abstractions.',
|
||||
'ponytail-review': 'Review a diff for over-engineering. Finds what to delete: reinvented stdlib, needless deps, speculative abstractions. One line per finding.',
|
||||
'ponytail-audit': 'Audit the whole repo for over-engineering. A ranked list of what to delete, simplify, or replace with stdlib or native features.',
|
||||
'ponytail-debt': 'Harvest every ponytail: shortcut comment into one debt ledger, so deferrals get tracked instead of forgotten. One-shot report.',
|
||||
'ponytail-help': "Quick reference for ponytail's modes, skills, and commands. One-shot display.",
|
||||
};
|
||||
|
||||
const NAMES = Object.keys(DESCRIPTIONS);
|
||||
|
||||
function sourceBody(name) {
|
||||
const src = fs.readFileSync(path.join(ROOT, 'skills', name, 'SKILL.md'), 'utf8').replace(/\r\n/g, '\n');
|
||||
const fm = src.match(/^---\n[\s\S]*?\n---\n?/);
|
||||
if (!fm) throw new Error(`skills/${name}/SKILL.md has no frontmatter`);
|
||||
return src.slice(fm[0].length);
|
||||
}
|
||||
|
||||
function render(name) {
|
||||
const desc = DESCRIPTIONS[name];
|
||||
if (desc.length > 160 || desc.includes('\n') || desc.includes('"')) {
|
||||
throw new Error(`description for ${name} must be one line, no quotes, under 160 chars`);
|
||||
}
|
||||
const frontmatter =
|
||||
`---\nname: ${name}\ndescription: "${desc}"\nhomepage: ${HOMEPAGE}\nlicense: MIT\n---\n`;
|
||||
return frontmatter + sourceBody(name);
|
||||
}
|
||||
|
||||
function outPath(name) {
|
||||
return path.join(ROOT, '.openclaw', 'skills', name, 'SKILL.md');
|
||||
}
|
||||
|
||||
module.exports = { DESCRIPTIONS, NAMES, render, outPath, sourceBody };
|
||||
|
||||
if (require.main === module) {
|
||||
for (const name of NAMES) {
|
||||
const p = outPath(name);
|
||||
fs.mkdirSync(path.dirname(p), { recursive: true });
|
||||
fs.writeFileSync(p, render(name));
|
||||
console.log('wrote', path.relative(ROOT, p).replace(/\\/g, '/'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user