Back to skills

Prune

Code

Prune

Rule zero: treat every declaration as live until you have proven it safe to remove.

Find candidates in four categories. Verify each candidate across the whole repository, code and tests alike. Report all findings for approval before editing.

Categories

  1. Confirmed dead: symbols, constants, tokens, or barrel re-exports with zero references.
  2. Dedupe: the same constant, string, or type literal declared verbatim in two or more places.
  3. Drift: a magic literal that duplicates an existing named constant.
  4. Arbitrary limits: caps, thresholds, or timeouts guarding against situations users cannot reach. Quantify the real limit before recommending removal.

Workflow checklist

Copy this checklist and track your progress:

Prune Progress:

  • 1. Build the candidate list (tooling + search)
  • 2. Verify every candidate repo-wide
  • 3. Classify: safe to remove / arbitrary but intentional
  • 4. Present the findings table
  • 5. Ask which findings to apply
  • 6. Apply approved changes and update tests
  • 7. Run typecheck, lint, and tests

1. Build the candidate list

Use existing tooling. Do not install new tools without asking the user.

For JavaScript and TypeScript projects:

  • If the project already uses knip, ts-prune, or depcheck, use their output as the candidate list. For example: npx knip.
  • Otherwise, check whether TypeScript flags unused locals (noUnusedLocals, noUnusedParameters) or ESLint's no-unused-vars are enabled. Use their warnings as candidates.

Tool output is a candidate list only. Every candidate must still pass the verification step.

2. Verify every candidate

  1. Search each category across the whole repository: code, tests, and config.
  2. Verify every candidate's usage before judging it. One missed reference makes the verdict wrong.
  3. For symbols re-exported by barrel files: a symbol is not dead while its barrel has importers. Search for the symbol name, then follow each importing file through the barrel chain. A naive search that does not resolve re-exports will report a false "live" status.
  4. Classify each candidate as safe to remove or arbitrary but intentional.

3. Present findings

Use exactly these columns:

ItemLocationNotes
<symbol>path:lineZero references repo-wide (verified). Confirmed dead.
<constant>pathA:line, pathB:lineIdentical declaration in 2 files. Dedupe into one shared constant.
literal <value>path:lineDuplicates <NAMED_CONSTANT>; reference the constant to avoid drift.
<CAP>path:lineThreshold unreachable in practice (state the real limit). Safe to remove.
  • Item: exact declaration in backticks, or a short phrase for inline literals.
  • Location: path:line. Include every dedupe and drift location.
  • Notes: one sentence. Identify the item, confirm usage, and give the verdict.

Output the table. Stop. Do not edit until the user replies with approved row numbers.

4. Apply and verify

  1. Apply approved changes and update affected tests.
  2. Run typecheck, lint, and tests. Fix anything the changes broke.

Treat as live

These are never dead without extra proof:

  • Entry points: package.json main and bin fields, framework pages and routes, serverless handlers.
  • Public API packages: exports consumed by other packages or users.
  • Dynamic access: eval, new Function, reflection, dynamic import(), glob require, and property access by string.
  • Framework entry files and convention-based registrations.
  • Code used only by tests, if removing it breaks the tests.

Edge cases

  • Monorepos: verify references across every package, not only the package that declares the symbol. Workspace hoisting can move consumers.
  • Generated code: skip generated output (lockfiles, compiled output, vendored dependencies) when searching, but flag generated files that re-export dead code if the generator is yours to change.
  • Environment-conditional code: a branch gated by process.env or a build flag is not dead because your current environment never runs it. Report it as arbitrary but intentional.
  • Commented-out code: that is dead code, not a comment. Report it as a separate finding and let the user decide.

Rules

When in doubt, it stays live. Remove only what you have proven dead.

  • A symbol used only in its own file may be unexported, not deleted. Cross-file consumers require it to stay exported.
  • Check tests before removing exports. Keep exports that cover non-trivial logic, or flag the trade-off.
  • Keep intentional tuning values: animation timings, debounce intervals, sizes, minimums. Report them as arbitrary but intentional.
  • Keep validation and guards that prevent crashes on corrupt or missing data.
  • Treat uncertain dynamic access, reflection, constructed imports, public APIs, and framework entry points as live.

See examples/findings-report.md for a worked report.

View on GitHub