| name | Skillpacks |
| description | Cluster the skills/ corpus into navigable packs (discovery view + install manifests + chain proposals); PR only when membership shifts |
| var | |
| tags | ["meta","docs"] |
Today is ${today}. Re-cluster the fleet of skills into coherent packs. Skip notify and PR when membership hasn't shifted — clustering changes slowly on a stable fleet, so silence is the expected weekly outcome.
Steps
1. Fingerprint inputs and check for change
Build a fingerprint over every tracked skills/*/SKILL.md plus the extractor itself:
{
git ls-files -- 'skills/*/SKILL.md' | sort | xargs sha1sum
sha1sum scripts/skillpacks.mjs
} | sha1sum | awk '{print $1}' > /tmp/skillpacks.fingerprint
Compare against memory/state/skillpacks.json (key input_fingerprint). If identical:
- Exit silently. No notify. No PR. No file rewrite. Log a single line:
skillpacks: no input change, skipping.
2. Run the extractor
node scripts/skillpacks.mjs
Writes:
skillpacks.json
docs/skillpacks.md
docs/skillpacks.html
docs/skillpacks-chains.md
skillpacks/<slug>.json (one per pack; stale files from prior runs are wiped first)
It prints a one-line summary like skillpacks: 163 skills · 14 packs · 0 solos · 1234 edges — capture it.
3. Detect meaningful change
Diff the new skillpacks.json against HEAD:skillpacks.json. Compute:
skill_delta = current stats.skills − previous
pack_delta = current stats.packs − previous
solo_delta = current stats.solos − previous
moved_skills = skills whose pack-slug differs between previous and current (membership churn — this is the headline signal)
new_packs = pack slugs in current but not previous
dropped_packs = pack slugs in previous but not current
Set verdict_one_line:
new_packs.length > 0 → ${new_packs.length} new pack(s): ${new_packs[0]}…
dropped_packs.length > 0 → ${dropped_packs.length} pack(s) dissolved
moved_skills.length > 5 → ${moved_skills.length} skills regrouped
solo_delta > 0 → ${solo_delta} new solo(s) — investigate
- otherwise →
clustering refreshed (${current.stats.packs}p / ${current.stats.skills}s)
If git diff --quiet skillpacks.json docs/skillpacks.{md,html} docs/skillpacks-chains.md skillpacks/ reports no diff (extractor output deterministic), exit silently — update the fingerprint state, no PR.
4. Open PR
git checkout -b skillpacks/${today} 2>/dev/null || git checkout skillpacks/${today}
git add skillpacks.json docs/skillpacks.md docs/skillpacks.html docs/skillpacks-chains.md skillpacks/
git commit -m "skillpacks: ${verdict_one_line}"
git push -u origin skillpacks/${today}
gh pr create \
--title "skillpacks: ${verdict_one_line}" \
--body "Auto-regenerated by the \`skillpacks\` skill on ${today}.
Stats: ${current.stats.skills} skills · ${current.stats.packs} packs · ${current.stats.solos} solos · ${current.stats.edges} edges
Delta: ${skill_delta:+0} skills, ${pack_delta:+0} packs, ${solo_delta:+0} solos
$( [ -n \"$new_packs\" ] && echo \"**New packs:** $new_packs\" )
$( [ -n \"$dropped_packs\" ] && echo \"**Dropped packs:** $dropped_packs\" )
$( [ -n \"$moved_skills\" ] && echo \"**Skills that changed packs:** $moved_skills\" )
Discovery: \`docs/skillpacks.md\` / \`docs/skillpacks.html\`. Chain proposals: \`docs/skillpacks-chains.md\`."
5. Persist state and notify
Write memory/state/skillpacks.json:
{
"input_fingerprint": "<from step 1>",
"last_run": "${today}",
"stats": { ... current.stats ... },
"last_verdict": "${verdict_one_line}",
"last_pr": "${pr_url}",
"pack_membership": { "<pack-slug>": ["<skill-slug>", ...] }
}
The pack_membership snapshot is what enables moved_skills detection on the next run — without it, every regenerate looks like total churn.
Notify (concise, one line):
./notify "*Skillpacks updated* — ${verdict_one_line}. PR: ${pr_url}"
Skip the notify when verdict_one_line is the bland clustering refreshed (…) form and moved_skills.length === 0 — that's just regeneration without meaningful structural change.
Sandbox note
Pure local work — no outbound network from bash. node scripts/skillpacks.mjs reads tracked SKILL.md files only via git ls-files, and gh pr create handles its own auth. No prefetch or post-process needed.
Exit modes
SKILLPACKS_NO_CHANGE — fingerprint matched, silent exit (the expected weekly path on a stable fleet)
SKILLPACKS_OK — regenerated + PR opened; notify only when membership shifted
SKILLPACKS_NEW_PACK — at least one new pack appeared; always notify
SKILLPACKS_DROPPED_PACK — at least one pack dissolved; always notify (usually means several skills were deleted or significantly rewritten)
SKILLPACKS_ERROR — extractor crashed or git op failed; notify with the error and exit non-zero