skill-hygiene
Audit and maintain a skill bundle so every bundled asset is reachable from SKILL.md or its directory's INDEX.md. Use when a skill has accumulated content (references, examples, schemas, scripts, templ
Files13
View on GitHubSKILL.mdskills/skill-hygiene/SKILL.md
GitHubSkill Hygiene
A skill bundle is a tree of files. Only the ones SKILL.md (or a SKILL-loaded INDEX.md) names are actually discoverable by an agent at runtime. Files that are not named are dead weight: they cost disk, they look like they're working, and they're not.
This skill is for keeping a bundle's reachable surface in sync with what's on disk — and for catching the drift before it ships.
NOT For
- Creating new skills. That's
skill-architect. - Grading skill quality (clarity, expertise level, examples). That's
skill-grader. - Generic doc linting unrelated to skill-bundle structure.
- Fixing prose. The auditor checks reachability, not whether the prose is good.
When To Use
- A skill has grown several subdirectories and you suspect not all of them are referenced from SKILL.md.
- You added a new file to an existing skill and want to confirm it's discoverable.
- You inherited a skill from another author and don't trust the surface area.
- You're about to publish or mirror a skill and want a clean reachability check first.
- A new INDEX.md was written by hand and may have drifted from the directory.
The Drift Modes
A skill bundle drifts in four ways. The auditor catches all four. A fifth issue type — broken links — surfaces typos and renames before they ship.
1. Orphaned files
A doc file exists in a subdirectory but no SKILL.md or INDEX.md mentions it.
Agents loading the skill at runtime will never find it. Asset files (.svg,
.png, fonts, etc.) are not audited as orphans — they're consumed by docs
that reference them, not loaded directly.
2. INDEX drift
An INDEX.md lists entries that don't match the directory:
- missing_from_index: doc files on disk the index doesn't mention (the orphan's mirror — same problem, viewed from the index side).
- ghost_entries: files in the index's table-row first column that aren't on disk anywhere in the bundle. Filename was changed or deleted; the index wasn't updated.
3. Broken links
A markdown link [text](path) or reference-style link from SKILL.md or any
INDEX.md points at a file that doesn't exist at the resolved path. The
auditor reports the source, target, and fuzzy-matched suggestions ("did you
mean: respawn.md?") for likely typos.
4. Missing INDEX
A subdirectory has multiple bundled doc files but no INDEX.md. Two
severities:
- Failure: at least one file in the directory is not named in SKILL.md. Without an INDEX hub, those files are unreachable.
- Warning: every file is named directly in SKILL.md. The skill works, but adding an INDEX would let SKILL.md route by trigger instead of listing files individually.
The Audit Procedure
python3 skills/skill-hygiene/scripts/audit_skill_bundle.py <path-to-skill-bundle>
The script:
- Walks the bundle, treating each top-level subdirectory as an audit unit.
- Parses markdown links (inline
[text](path), reference-style, backtick code spans) from SKILL.md and every INDEX.md. - Resolves each link relative to its containing file. Classifies as
ok/broken/external/anchor/outside. - Builds a "referenced" set from successfully resolved links plus basenames mentioned in prose anywhere reachable.
- Any doc file not in that set → orphan (assets are exempt).
- Any link with status
broken→ broken_link with typo suggestions. - For each existing INDEX.md, checks every same-directory doc file appears in the index → flags missing_from_index.
- For each existing INDEX.md, scans pipe-table first-column entries for filenames not on disk anywhere → flags ghost_entries.
- For each subdirectory with >1 doc file but no INDEX.md → flags missing_index (failure or warning depending on whether SKILL.md already names every file in the directory).
Exit code is 0 if clean, 1 if any failing issue, 2 if the bundle is
malformed (no SKILL.md). Use --json for CI integration, --quiet to set
exit code only.
Fixing Drift
| Drift | Fix |
|---|---|
| Orphan in a directory that has an INDEX.md | Add the file to INDEX.md with a one-line "when to load" hook. |
| Orphan in a directory that has no INDEX.md | Either create an INDEX.md (preferred when the directory will keep growing) or mention the file directly in SKILL.md. |
| Missing-from-index | Same fix as orphan in directory with INDEX.md. |
| Ghost entry | Either the file should exist (restore it) or the entry is stale (delete the row). Check git log for the filename to decide. |
| Missing INDEX | Create one. Use the table format shown in references/progressive-disclosure.md. |
Doctrine
- SKILL.md is the entry point, not the encyclopedia. Don't inline what belongs in a leaf doc; instead, point at the right INDEX.md with a one-line trigger ("when X, open Y").
- Every subdirectory with more than one file needs an INDEX.md. The cost is one short table; the benefit is discoverability.
- An INDEX.md row is a contract: "this file exists, here's when to load it." If the file moves, the contract breaks. Run the auditor to catch it.
- Cross-references are fine. An INDEX.md can mention a file in another
directory (e.g.
agents/INDEX.mdmentioningsubagent-fork-template.yaml). The auditor only flags ghost entries when the filename exists nowhere in the bundle.
See references/progressive-disclosure.md for the full SKILL.md → INDEX.md →
leaf pattern, and references/drift-prevention.md for hooking the auditor
into pre-commit and CI.
Pairing With Other Skills
- After
skill-architectproduces a fresh bundle, run skill-hygiene to confirm every file the architect emitted is actually reachable. - After
skill-graderflags "this skill could use more examples" and you add them, run skill-hygiene to confirm the new examples are surfaced. - Before mirroring a skill into other tool roots (
.codex/,.claude/,.gemini/extensions/), audit the canonical bundle first — drift mirrors too.
Self-Check
python3 skills/skill-hygiene/scripts/audit_skill_bundle.py skills/skill-hygiene
The hygiene skill audits itself. It should always pass; if it doesn't, fix its own surface area before fixing anyone else's.
Installing The Pre-Commit Guard
The pre-commit hook is the highest-leverage drift catcher: it runs the auditor on every skill bundle touched by a commit and blocks the commit if any bundle fails. Install once per checkout:
bash skills/skill-hygiene/scripts/install_hooks.sh
The installer is idempotent. It writes to the shared common-hooks dir
(git rev-parse --git-common-dir), so linked git worktrees pick it up
automatically. Bypass once with git commit --no-verify when the drift is
intentional and a follow-up fix is queued.
Healing Drift In Bulk
For libraries with many drifted skills, see scripts/heal_skill_bundle.py.
It reads the audit findings, packages SKILL.md + every subdirectory's
content into a prompt, and dispatches to claude (haiku for one-orphan
quick wins, sonnet for content integration). The model emits a JSON write
plan; the script applies it and re-runs the auditor to verify. Use
--apply to mutate; default is dry-run.
Healer auth requirements. heal_skill_bundle.py shells out to the
claude CLI. That binary must be installed and authed on the operator's
machine — there's no API-key-only fallback today. Concretely: CI cannot
run the healer. CI catches drift via the auditor; the operator fixes drift
locally with the healer, commits, and pushes. If a contributor doesn't
have a working claude CLI, they hand-fix the failing bundle or wait for
someone with credits to run the healer.
python3 skills/skill-hygiene/scripts/heal_skill_bundle.py skills/<name> --model sonnet --apply
Mis-paired content (frontmatter says X, content is about Y) is out of scope for the healer — that needs human review.