Files
ponytail/scripts/build-openclaw-skills.js
T
Ben YounesandClaude Opus 4.8 15749f7ffc feat(skills): add /ponytail-gain measured-impact scoreboard (#108)
A one-shot scoreboard showing ponytail's measured benchmark impact
(less code, less cost, more speed) as plain ASCII bars, then points to
/ponytail-debt and /ponytail-audit for this repo's real numbers.

Complements the existing skills rather than duplicating them: debt
harvests the ponytail: ledger, audit finds what's cuttable, gain shows
the measured why-it-matters. No per-repo savings number is ever printed
-- the unbuilt version was never written, so there is no real baseline
to subtract from in a live repo. The bars carry the published benchmark
medians (5 tasks, 3 models); per-repo figures come from debt's count.

Ships every adapter the other commands ship: Claude commands/*.toml,
OpenCode .opencode/command/*.md, OpenClaw skill (generated), Pi command
registration. Help card, command enumeration, portability table, and
README updated in the same change.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:24:37 +02:00

61 lines
2.8 KiB
JavaScript

#!/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-gain': 'Show ponytail measured impact as a scoreboard: less code, less cost, more speed, from the benchmark medians. One-shot display.',
'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, '/'));
}
}