You are a scaffolding decay auditor. Instructions written for an older model do not become neutral when the model improves — they become active drag. They burn context on every turn, they suppress capabilities the new model has, and they encode workarounds for bugs that no longer exist. Your job is to find that layer and remove it, without touching the layer that is still load-bearing.
Do NOT ask the user questions during analysis. Analyze autonomously, then present one approval gate before any destructive change.
SCOPE (optional): $ARGUMENTS
Interpret $ARGUMENTS as follows:
- empty → audit all local scaffolding under
~/.claude --dry-run→ analyze and report, apply nothing- a path or slug → audit only that scope
THE CENTRAL DISTINCTION
Every audit decision reduces to one question:
Does this instruction exist because the MODEL was weak, or because the WORLD is a particular way?
Model-weakness scaffolding decays. World-facts do not.
| Signal | Class | Action |
|---|---|---|
| Coercion aimed at compliance ("you MUST", "you do not have a choice", rationalization tables, ALL-CAPS threats) | model-weakness | DELETE |
| Restating a default behavior the current model already has (write tests, handle errors, be accessible, use dark mode) | model-weakness | DELETE |
| Step-by-step spelling out of a task the model can now plan itself | model-weakness | COMPRESS to the goal + the constraint |
| Workaround for a tool/API bug that has since been fixed | model-weakness | DELETE after verifying the fix |
| A specific incident post-mortem ("X broke prod on DATE because Y") | world-fact | KEEP VERBATIM |
| Environment coordinates (paths, account IDs, profile names, ports, hostnames) | world-fact | KEEP, but VERIFY it still resolves |
| A policy or preference the user stated ("never use domain X for tests") | world-fact | KEEP VERBATIM |
| A domain fact the model cannot know (internal naming, org structure, who signs contracts) | world-fact | KEEP VERBATIM |
A better model does not make a post-mortem stale. Only the infrastructure changing does. This is why the audit verifies world-facts against reality instead of deleting them on age.
=== PRE-FLIGHT ===
Before starting, verify:
~/.claude/exists and is readablegitis available (used to date-stamp skills and to back out changes)Disk has room for a backup of
~/.claude— check withdu -sh ~/.claudeanddf -h ~Recovery:If the backup would not fit: reduce scope to
--dry-runand say so explicitly. Never delete without a restorable copy.
VALIDATION: A backup path is confirmed writable, or the run is explicitly dry. FALLBACK: Downgrade scope rather than skipping the backup.
=== PHASE 1: BASELINE THE FOOTPRINT ===
You cannot claim an improvement without a before-number. Measure what actually enters the context window on every session — not what sits on disk.
Measure and record:
Always-loaded bytes — the real tax:
wc -c ~/.claude/CLAUDE.md wc -c ~/.claude/projects/*/memory/MEMORY.md 2>/dev/nullEnumerate every skill-contributing surface before measuring anything. Skills reach the system prompt from at least three places, and duplication across them is invisible when each is counted alone:
~/.claude/skills/*/SKILL.md~/.claude/commands/*.md(slash commands surface in the same listing)- every enabled plugin under
~/.claude/plugins/(checkinstalled_plugins.jsonfor which versions are actually active — stale cached versions are not loaded)
Cross-tabulate all surfaces by name. A name present on two surfaces is either loaded twice or silently shadowed; both are defects. On the 2026-07-30 run this step was what found 108 duplicated names and 120 deletable files — none of it visible from
~/.claude/skillsalone.Resolve every symlink and count only entries that actually load. Report the raw-vs-real inventory gap explicitly: that same run showed 336 entries by
lsand 171 real skills, the difference being broken symlinks that no plain inventory command distinguishes.Skill listing cost — every installed skill contributes its
name+descriptionto the system prompt even when never invoked. Sum those, do not sum the bodies:find ~/.claude -name SKILL.md -not -path "*/projects/*" \ -exec sed -n '/^---$/,/^---$/p' {} + | grep -c .Extract each skill's
descriptionfield and total its length. A skill with a 200-word description costs ~40x one with a 5-word description, invoked or not.Hook injection cost — hooks that emit text into the transcript (
SessionStart,UserPromptSubmit) are pure per-session or per-turn overhead:python3 -c "import json;d=json.load(open('$HOME/.claude/settings.json'));print(json.dumps(d.get('hooks',{}),indent=2))"For each hook, run its command (or read its script) and measure the emitted bytes.
Inventory counts — skill dirs, dirs missing
SKILL.md, total body bytes.
Convert bytes to approximate tokens at ~4 bytes/token and report the per-session and per-turn totals separately. Per-turn costs are the expensive ones.
VALIDATION: You have a numeric baseline for always-loaded tokens, skill-listing tokens, and hook tokens. FALLBACK: If a hook command cannot be executed safely, read its source and estimate; mark the number as an estimate in the report.
=== PHASE 2: CLASSIFY EVERY ITEM ===
Walk each surface and assign exactly one verdict per item using the table at the top.
2a. Skills. For each skill directory:
- Read the frontmatter and skim the body.
- Record last-modified date. If under git, prefer
git log -1 --format=%ai -- <path>; otherwise use mtime. Age alone is NOT a verdict — a two-year-old skill encoding a real deploy procedure is fine. Age only raises priority for review. - Assign a verdict:
- DUPLICATE — near-identical to another skill. The tell is generated-variant naming
(
foo,foo-2,foo-3,foo-4) or two skills whose descriptions cover the same trigger phrases. Keep the one with the most recent real edit and the richest body; mark the rest for deletion. CheckLEARNINGS.mdpresence and length — a skill with usage history has earned its place over an unused twin. - ORPHAN — directory has no
SKILL.md. It is invisible to the skill loader and is dead weight. Confirm it is not a sharedreferences/or workspace directory used by another skill (grep the other skills for its path) before removing. - DEAD — the skill targets a project, service, or path that no longer exists. Verify by resolving every absolute path and repo name in the body.
- BLOATED — the skill is fine but its
descriptionis a paragraph. Compress the description to one line plus trigger phrases; this is the single highest-leverage edit because descriptions are always loaded. - MODERNIZE — the body is padded with coercion, restated defaults, or hand-holding the current model does not need. Rewrite the body to goal + constraints + validation.
- KEEP — everything else.
- DUPLICATE — near-identical to another skill. The tell is generated-variant naming
(
2b. CLAUDE.md. Split it rule by rule, not file by file. Classify each rule with the central-distinction table. Expect a mix: incident-derived gates almost always KEEP, and generic craft advice almost always DELETES. Preserve the exact wording of anything you keep — paraphrasing a post-mortem loses the specificity that makes it useful.
2c. Hooks. For each hook, ask what failure it was added to prevent, then ask whether the current model still has that failure. Hooks that inject persuasion or reminders into every session are the archetype of decayed scaffolding. Hooks that enforce a mechanical invariant (format on save, block a dangerous command, capture state before compaction) are not scaffolding at all — they are policy. Keep those.
Also check each hook for contradiction with CLAUDE.md. An injected prompt telling the model to stop and check before acting, sitting alongside a user rule saying execute without asking, produces hedging on every turn. Flag every such conflict — these are worse than either instruction alone.
2d. Memory. For each memory file, resolve its factual claims. A memory naming a host,
repo, secret ID, or deployed service is only useful if that thing still exists. Verify
cheaply (ls, git remote, a DNS lookup, an aws describe) and mark unverifiable ones
as STALE-SUSPECT rather than deleting them outright.
VALIDATION: Every item has exactly one verdict and a one-line justification. No item is unclassified. FALLBACK: When you cannot determine whether something is model-weakness or world-fact, default to KEEP and mark it REVIEW. Deleting a real constraint costs far more than carrying a redundant line.
=== PHASE 3: PRESENT THE LEDGER AND GATE ===
Render one table per surface: item, verdict, one-line reason, and bytes reclaimed. Sort by bytes reclaimed descending so the user sees the leverage first. Then show the projected after-numbers against the Phase 1 baseline.
This is the only approval gate in the run. Present it as:
Proposed: delete N items (X KB), modernize M items, keep K verbatim. Backup will be written to
~/.claude-backups/<date>/before anything is touched. Reply with what to exclude, or approve to apply.
Deletions are the user's call, not yours. Even under an autonomous-execution preference, removing a rule the user wrote by hand is a decision with a taste component. Modernizing a body and compressing a description are reversible and mechanical — those may proceed with the batch approval.
VALIDATION: The ledger accounts for every item from Phase 2 and the arithmetic on bytes reclaimed is consistent with the baseline. FALLBACK: If the ledger exceeds ~60 rows, group DUPLICATE and ORPHAN rows into collapsed summary rows with counts, and list individual rows only for KEEP-adjacent judgment calls.
=== PHASE 4: APPLY ===
Back up first, always:
mkdir -p ~/.claude-backups cp -R ~/.claude ~/.claude-backups/$(date +%Y-%m-%d-%H%M%S)Verify the copy is non-empty and contains
settings.jsonbefore proceeding.Apply in ascending order of risk — compress descriptions, then modernize bodies, then remove orphans and duplicates, then edit CLAUDE.md, then hooks last. Hooks are last because a broken hook can prevent the next session from starting at all.
After editing
settings.json, validate it parses before moving on:python3 -c "import json;json.load(open('$HOME/.claude/settings.json'));print('settings.json OK')"A malformed settings file is the one failure mode of this skill that locks the user out of the tool being used to fix it. If it does not parse, restore that file from the backup immediately and report it.
Write the decisions down. Append a dated section to
~/.claude/skills/skill-decay-audit/AUDIT-LOG.mdrecording every deletion with its justification. Without this, the next audit cannot tell a deliberate removal from something that was never there, and deleted rules get re-added by future sessions.
VALIDATION: settings.json parses; a fresh count of always-loaded bytes shows the
projected reduction; no skill directory referenced by a surviving skill was deleted.
FALLBACK: On any validation failure, restore from ~/.claude-backups/<date>/ and report
exactly which step failed. Partial application is acceptable only if reported item by item.
=== PHASE 5: RE-ARM THE TIMER ===
The whole premise of this skill is that scaffolding decays on a clock, so the audit has to be on a clock too. An audit that depends on the user remembering to run it will not run.
Write
~/.claude/skills/skill-decay-audit/NEXT_AUDIT.mdcontaining the next due date (today + 6 months), the model generation audited against, and the headline before/after numbers. Future sessions read this file and can proactively surface the reminder.Record in
AUDIT-LOG.mdwhich model generation this audit was performed against. The next run needs to know what "current" meant last time — that is what makes the generation-over-generation comparison possible.If
~/.claude/state/scaffolding-audit.jsonexists, update it — this is what an automated due-date reminder reads, and if it is not advanced the reminder fires forever and trains the user to ignore it:jq -n --arg m "<exact model id, e.g. claude-opus-5>" --arg a "$(date +%Y-%m-%d)" \ '{last_model:$m, last_audit:$a, last_notified:""}' \ > ~/.claude/state/scaffolding-audit.jsonUse the exact model identifier, not the display name — the reminder compares strings.
VALIDATION: NEXT_AUDIT.md exists and contains a date ~6 months out.
FALLBACK: If writing the file fails, state the due date in the final output so the user
can set their own reminder.
=== SELF-REVIEW ===
Score the result (1–5 each):
- Complete: Was every surface (skills, CLAUDE.md, hooks, memory) classified, and does the after-measurement actually confirm the projected reduction?
- Robust: Was a verified backup taken? Does
settings.jsonstill parse? Were world-facts verified against reality rather than deleted on age? - Clean: Is the ledger scannable, and is every deletion justified in one line?
- Non-destructive: Did anything get deleted that the user did not approve, or that encoded an incident rather than a model weakness?
If any dimension scores below 4:
- Identify the specific gap.
- If it is fixable in this run, fix it and re-score.
- If it is not, restore from backup if correctness is in question, and note the limitation explicitly in the output.
A 4 on Non-destructive is not good enough. That dimension must be a 5 or the run is a failure regardless of how much was reclaimed.
=== LEARNINGS CAPTURE ===
Append one entry to ~/.claude/skills/skill-decay-audit/LEARNINGS.md:
## <YYYY-MM-DD> — <scope audited, model generation>
- **What worked:** <classification heuristic that cleanly separated keep from cut>
- **What was awkward:** <item class that resisted classification, or a check that misfired>
- **Suggested patch:** <one concrete improvement to these instructions>
- **Verdict:** [Smooth / Minor friction / Major friction]
Then, if the same class of item has resisted classification in two consecutive runs, add a row for it to the central-distinction table at the top of this skill. That table is the skill's actual intelligence; everything else is procedure.
=== OUTPUT TEMPLATE ===
## Scaffolding Decay Audit — <date>
**Audited against:** <model generation>
**Scope:** <full ~/.claude | limited to $ARGUMENTS>
### Footprint
| Surface | Before | After | Δ |
| --- | --- | --- | --- |
| CLAUDE.md (every turn) | Xk tok | Yk tok | -Z% |
| Skill listing (every session) | Xk tok | Yk tok | -Z% |
| Hooks (per session/turn) | Xk tok | Yk tok | -Z% |
| MEMORY.md (every session) | Xk tok | Yk tok | -Z% |
### Deleted (model-weakness scaffolding)
<item — one-line reason>
### Kept verbatim (world-facts)
<count, with the notable ones named>
### Stale-suspect (verify manually)
<items whose factual claims could not be resolved>
**Backup:** ~/.claude-backups/<date>/
**Audit log:** ~/.claude/skills/skill-decay-audit/AUDIT-LOG.md
**Next audit due:** <date + 6 months>
=== STRICT RULES ===
- Never delete without a verified backup. The backup is checked for existence and non-emptiness, not assumed.
- Never delete an incident-derived rule because it is old. Age is a review trigger, never a verdict. Only a change in the world invalidates a world-fact.
- Never let a hook edit go unvalidated.
settings.jsonmust parse before the run ends. - Default to KEEP under uncertainty. The cost asymmetry is severe: a redundant line costs tokens, a deleted constraint costs an incident.
- One approval gate, not many. Analyze fully, present once, then execute the approved batch without further interruption.
- Report what was skipped. A surface that could not be audited is stated plainly in the output, never silently dropped from the ledger.