blob: dc9cffcd8e9f8ca698610bb498d29bd8a871aac7 [file]
#!/usr/bin/env node
// Generate per-family skill counts from a magpie `skills/` directory.
//
// The website's skill-family counts used to be hand-edited in
// ImmersiveGradientHero.tsx and drifted every time a skill landed upstream.
// This script derives the counts from the actual SKILL.md directories so they
// stay in sync with apache/magpie. It is invoked by scripts/sync-docs.sh after
// the framework repo is checked out.
//
// Usage: node gen-skill-counts.mjs <skills-dir> <out-json>
//
// Output is deterministic (no timestamps) so the committed JSON only changes
// when the counts actually change.
import { readdirSync, statSync, existsSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const [, , skillsDir, outPath] = process.argv;
if (!skillsDir || !outPath) {
console.error("usage: gen-skill-counts.mjs <skills-dir> <out-json>");
process.exit(1);
}
// Skills whose family is NOT encoded in their name prefix.
const OVERRIDES = {
// utilities — framework meta-skills
"write-skill": "utilities",
"list-skills": "utilities",
"optimize-skill": "utilities",
// mentoring skill that keeps the pr-management- prefix
"pr-management-mentor": "mentoring",
// repo-health — not name-prefixed
"ci-runner-audit": "repo-health",
"dependency-audit": "repo-health",
"workflow-security-audit": "repo-health",
"audit-finding-fix": "repo-health",
"license-compliance-audit": "repo-health",
"flaky-test-triage": "repo-health",
// contributor-growth — not name-prefixed
"committer-onboarding": "contributor-growth",
"contributor-activity-sweep": "contributor-growth",
"contributor-nomination": "contributor-growth",
"contributor-to-committer": "contributor-growth",
"good-first-issue-author": "contributor-growth",
};
// Name-prefix -> family. Checked in order; first match wins.
const PREFIXES = [
["pr-management-", "pr-management"],
["security-", "security"],
["release-", "release-management"],
["mentoring-", "mentoring"],
["pairing-", "pairing"],
["setup-", "setup"],
["setup", "setup"],
["issue-", "issue"],
];
function classify(name) {
if (OVERRIDES[name]) return OVERRIDES[name];
for (const [prefix, family] of PREFIXES) {
if (name === prefix || name.startsWith(prefix)) return family;
}
return null;
}
const dirs = readdirSync(skillsDir)
.filter((d) => {
try {
return (
statSync(join(skillsDir, d)).isDirectory() &&
existsSync(join(skillsDir, d, "SKILL.md"))
);
} catch {
return false;
}
})
.sort();
const counts = {};
const uncategorized = [];
for (const name of dirs) {
const family = classify(name);
if (!family) {
uncategorized.push(name);
continue;
}
counts[family] = (counts[family] || 0) + 1;
}
if (uncategorized.length) {
console.warn(
`⚠ ${uncategorized.length} skill(s) matched no family: ${uncategorized.join(", ")}`,
);
}
// Sort family keys for stable, diff-friendly output.
const sortedCounts = Object.fromEntries(
Object.keys(counts)
.sort()
.map((k) => [k, counts[k]]),
);
const out = {
_comment:
"GENERATED by scripts/gen-skill-counts.mjs — do not edit by hand. Counts derive from apache/magpie skills/.",
total: dirs.length,
uncategorized,
counts: sortedCounts,
};
writeFileSync(outPath, JSON.stringify(out, null, 2) + "\n");
console.log(`✓ skill counts → ${outPath}: ${JSON.stringify(sortedCounts)}`);