Promptheus/commands36 commands · free · CC0Promptheus hub ↗
← All commands
/cleanup [path]

Refactor

Clean up

Dead code, unused imports and dupes — gone.

cleanupdead-code
CategoryRefactor
Arguments[path]
Allowed toolsReadGrepGlobEdit
What it does

Remove dead code, unused imports/exports/deps and duplication within the given scope — no behaviour change.

.claude/commands/cleanup.md.claude/commands/ (project) · ~/.claude/commands/ (global)
Install into your repo
npx promptheus-commands add cleanup

The prompt

Remove dead code, unused imports/exports/variables/dependencies, and duplication from the target code with zero behaviour change, then prove nothing broke with the typechecker and tests.

Scope

Target: $ARGUMENTS. If empty, operate only on recently changed code. If the target is inside a git work tree, take the union of git status --porcelain and git diff HEAD --name-only — but --porcelain prefixes each line with a two-char status code and a space (and renames appear as old -> new), so strip that and keep only the final pathname, then filter to existing source files. If it is not a git repo (git rev-parse --is-inside-work-tree fails), do not guess a scope — report that and ask for an explicit path. Never touch files outside this scope.

Method

  1. Baseline first. Detect the project's tooling from its manifests (package.json scripts, Makefile, pyproject.toml, Cargo.toml, go.mod, CI config). Run the typechecker and test suite once, unchanged, and record that they pass. If the baseline is already red, stop and report — you cannot distinguish your breakage from pre-existing breakage.
  2. Detect, don't guess. Prefer the project's own analyzers over manual reading: e.g. ESLint no-unused-vars, ts-prune/knip, depcheck, pyflakes/ruff F401, cargo +nightly udeps, go vet/staticcheck, deadcode. Use them to build the candidate list; confirm each candidate by reading the code.
  3. Remove in this order, re-running the typechecker after each batch — and the test suite too after any batch that removes exports, dependencies, or dead branches (anything the typechecker cannot catch): unused local variables and imports → unreferenced private functions/types/constants → unused exports (only after grepping the whole repo for the symbol) → unused dependencies (only after grepping for the package name, including string/dynamic requires) → dead branches. Only remove a branch you can prove is unreachable, not one that merely looks unused: a condition on a compile-time constant, code after an unconditional return/throw/exit, an arm of an exhausted enum/union, or a block the compiler's own unreachable-code diagnostic (TS allowUnreachableCode:false, go vet, clippy, ruff) already flags. Confirm with coverage data when available; if you cannot demonstrate unreachability, leave it.
  4. De-duplicate conservatively — this is the only step that adds code, so it is the only place you can introduce a regression; gate it hardest. Merge two blocks only if you can show equivalence concretely: they are token-for-token identical after normalizing local identifier names and whitespace, close over no differing free variables, and have the same side effects, thrown errors, and control flow. If you cannot demonstrate that, skip it — "looks similar" is not a reason. When you do extract, put the helper in the same module/layer, keep existing naming and file conventions, and run the full test suite immediately after each extraction (not just the typechecker). Do not build an abstraction to unify two call sites unless the duplication is exact and non-trivial.
  5. Verify. Re-run the full typechecker and test suite. If anything is red, revert the offending change — do not "fix" it; because you kept each batch independently revertible (see below), roll back the last batch, re-run, and step backward batch-by-batch to isolate the culprit rather than debugging in place. Run the linter/formatter to confirm no new warnings.

Hard rules

  • Never change observable behaviour: no changes to signatures of public/exported API, return values, thrown errors, logged output, side effects, or ordering. This is pure subtraction and consolidation.
  • Never delete a symbol reachable non-lexically: dynamic dispatch, reflection, DI containers, string-keyed lookups, framework entry points (routes, handlers, migrations, CLI commands), serialization targets, public package exports, or test fixtures. When a static analyzer flags these, treat it as a false positive and keep them.
  • Never rename things, reorder logic, "improve" style, upgrade deps, or change formatting beyond what removal forces. Stay strictly on task.
  • Always grep the entire repository (not just the target path) before deleting any exported symbol or dependency — usages often live elsewhere.
  • Always keep each removal independently revertible; make small, focused commits/edits rather than one sweeping change.

Output

Report as a table grouped by category (unused imports, dead code, unused deps, duplication): file, symbol, and why it was safe to remove. Then a one-line net diff stat (files touched, lines removed) and explicit confirmation that the typechecker and tests pass — with the exact commands you ran. If you left a flagged candidate in place, list it and the non-lexical usage that saved it. If nothing was safely removable, do not print an empty table — state plainly that you found no dead code, unused symbols, or duplication in scope, and still report the baseline commands you ran and that they pass.

Add it to your toolkit

Save it as .claude/commands/cleanup.md or .cursor/commands/cleanup.md, then invoke it with /cleanup and your arguments.

Back to top ↑