Dead Code Finder
Find genuinely unused code — unreferenced exports, unreachable files, and unused dependencies — and remove it safely with build/test verification. Use when trimming a codebase or untangling years of accreted cruft.
npx agentscamp add skills/dead-code-finderInstall to ~/.claude/skills/dead-code-finder/SKILL.md
A skill that finds genuinely unused code — unreferenced exports, unreachable files, unused dependencies — and removes it safely: it walks from the real entry points, runs ecosystem tools like knip or vulture, greps the whole repo for dynamic usage before trusting any candidate, then deletes one item at a time, re-running build and tests after each.
Hunt down code that nothing references and delete it without breaking the build. The skill walks the dependency graph from the project's real entry points, flags exports no module imports, files no path reaches, and dependencies no source line uses — then removes them one at a time, re-running the build and tests after each deletion so a false positive surfaces immediately instead of in production.
When to use this skill
- A codebase has accumulated dead exports, orphaned files, or leftover utilities after refactors and feature removals.
package.jsonlists dependencies you suspect nothing imports anymore.- You want a measured, verifiable cleanup pass — not a risky bulk delete.
WARNING
"Unreferenced" is not the same as "unused." Code can be reached at runtime in ways static search misses: string-based requires (require(./handlers/${name})), reflection/DI containers, framework entrypoints (route files, migrations, CLI commands, test setup), config-driven plugin loading, and anything that is part of a published public API. Treat these as live until proven otherwise. Verify every removal with the build and tests before moving to the next one.
Instructions
- Locate the entry points. Identify where execution actually begins —
main/exports/bininpackage.json,next.config/route conventions,if __name__ == "__main__", CLI definitions, test runners. Everything reachable from these is live; the dead set is the complement. - Detect the right tooling — do not guess. Match the ecosystem and prefer purpose-built tools over hand-rolled grep:
- TS/JS:
knip(exports, files, and deps in one pass),ts-prune,depcheck, oreslint'sno-unused-vars. - Python:
vulture,deptry,ruff check --select F401. - Go:
staticcheck/go vet,golangci-lint. Rust:cargo +nightly udeps, dead-code warnings. Read the config these tools already respect; honor existing ignore lists.
- TS/JS:
- Build the candidate list, then triage. For each candidate (unreferenced export, unreached file, unused dependency), grep the whole repo — including configs, test setup, CI scripts, dynamic-import strings, and docs — before trusting the tool. Drop anything matched by the dynamic-usage patterns in the warning above, and anything re-exported from a package's public entry point.
- Remove one thing at a time. Delete a single export/file/dependency, then run the project's build and test commands. Never batch deletions across the verification step — a green-then-red transition must point at exactly one change.
- Verify after each removal. Run the real commands (
npm run build && npm test,pytest,go build ./... && go test ./...). A clean build and passing suite is the proof. If anything breaks, revert that single change and mark the candidate as a live-via-dynamic-usage false positive. - Report and flag gaps. List what was removed (with the verifying command output), what was kept and why, and any candidates that need human judgment — public-API surface, generated code, or dynamic usage your search could not rule out.
NOTE
Run the cleanup on a branch and keep each removal as its own commit. If a deletion only surfaces a failure in CI or a downstream consumer, a granular history makes the exact revert trivial.
Examples
Confirming an export is truly unused before deleting it — formatLegacyDate in src/utils/date.ts:
# 1. Tool flags it as an unreferenced export
$ npx knip --include exports
src/utils/date.ts:42:14 - 'formatLegacyDate' is unused (exports)
# 2. Verify by hand across the WHOLE repo, including dynamic strings and configs
$ grep -rIn "formatLegacyDate" --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' .
src/utils/date.ts:42:export function formatLegacyDate(d: Date): string {
# Only the definition — no importers, no string references, no re-export in index.tsOne self-reference and nothing else: safe to delete. Remove it, then prove the codebase still compiles and passes:
$ npm run build && npm test
✓ build succeeded
✓ 214 passedContrast with a false positive — an export knip also flags, but grep finds reached dynamically:
$ grep -rIn "handlers/" --include='*.ts' .
src/router.ts:18: const mod = await import(`./handlers/${route.name}`);The static tool can't follow the template-literal import, so handlers/checkout.ts only looks orphaned. Keep it, document the dynamic load, and report it as a manual-review case rather than deleting it.
Related
- Refactoring SpecialistUse this agent to safely restructure code without changing behavior — extracting, renaming, decoupling. Examples — breaking up a god object, removing duplication, improving testability.
- RefactorRefactor the target for readability and structure without changing behavior.
- Git Blame InvestigatorReconstruct why a line of code exists from Git history — find the originating commit, read its message and full diff for intent, and see through reformatting/rename commits with ignore-revs and the pickaxe — before you change or delete it. Use when a line looks wrong or pointless and you want to remove it, when tracing a regression to its commit, or when onboarding to unfamiliar code.
- React Render ProfilerFind and fix wasteful React re-renders by classifying the cause — unstable prop/callback/object identities, context value churn, state lifted too high, expensive work in render, or unvirtualized lists — confirming it with a measurement, then applying the one targeted fix and re-measuring. Use when a React UI is janky, slow to type in, or re-renders far more than the data actually changed.
- Circular Dependency BreakerDetect and break a circular import — map the exact cycle with a real tool, then break the right edge by extracting the shared piece into a leaf module, inverting a layering dependency, merging two falsely-split modules, or (last resort) deferring an import. Use when you hit an import cycle error, an undefined-on-import or 'cannot access before initialization' bug, or a bundler/linter flags a cycle.
- Dependency Upgrade PlannerPlan and de-risk a major dependency, framework, or runtime upgrade — map the full version path, read every intermediate migration guide, and pin the breaking changes to your actual call sites instead of bumping the number and hoping. Use when a key dependency is several majors behind, when a security advisory forces an upgrade, or before a framework migration.
- Extract ModuleSplit an overgrown file into cohesive, well-bounded modules — find the natural seams, design each new module's public interface before moving a line, then relocate one unit at a time keeping tests green. Use when a file has grown too large, mixes unrelated responsibilities, or every change to it forces unrelated diffs and merge conflicts.
- Feature Flag RetirerRetire stale feature flags by confirming each flag's decided final state, then collapsing every conditional to the winning branch and deleting the loser plus the now-dead code it reached. Use when temporary flags have outlived their rollout, when flag conditionals clutter the code, or during a flag-debt cleanup.
- Type Coverage ImproverRaise TypeScript type strictness incrementally — measure the any/implicit-any baseline, enable one strict sub-flag at a time, and fix the fallout per flag instead of all at once, keeping the typecheck green at every step. Use when a codebase is loosely typed, when you want strict mode on without a big-bang break, or when `any` keeps hiding bugs that surface in production.
- SerenaAn MCP toolkit that gives coding agents IDE-grade powers — symbol-level retrieval and editing via language servers, across 40+ languages.