Cleaner
Behavior-preserving cleanup. Smaller files, no dead code, no duplication, same
behavior. The test suite ends green and the public surface is unchanged. If a
change would alter behavior, do not make it.
The Contract (non-negotiable)
- Tests first. Run the project's tests before touching anything. Record
green or red. If there is no test suite, say so and ask before proceeding.
- One change at a time. Apply one logical edit, re-run the tests, then
commit or revert. On the first failure you did not cause, stop and revert.
- Public surface frozen. Exported names, function signatures, types, and
module paths stay identical. Internal moves only.
- No dynamic guesses. Never delete code that may be reached by reflection,
string dispatch,
eval, dependency injection, or __all__-gated exports.
When unsure, keep it and flag it for the human.
- Every change gets a diff and a one-line reason. Group changes into a
reviewable set; nothing lands silently.
How it works
Assess, propose, execute. Use the detection scripts to find candidates, rank
them by safety, then apply one at a time with verification.
- Assess. Run
scripts/find_oversized.py, find_unused.py, and
find_duplicates.py, each with --json. Build a findings list.
- Propose. Rank by blast radius: pure-internal dead code first, then
duplication, then file or class splits last. State the order out loud.
- Execute. Apply one change, run the tests, commit or revert, repeat.
Operations
Dead code
Remove unused functions, imports, variables, types, and whole files. Confirm
zero references with a whole-repo grep, including inside strings and comments
(dynamic dispatch hides there). See references/cleanup-safety.md for the
per-language patterns that defeat a naive grep.
Split oversized files or classes
When a file or class is too large, extract cohesive units into new modules.
Re-exports keep the public surface stable: the old path re-exports from the new
location so callers and imports do not change. Run the tests after every move.
Deduplicate
When two or more blocks do the same thing, merge them into one shared helper.
The helper keeps the most general signature that preserves every existing call
site. Replace each duplicate with a call to the helper. Run the tests.
Red flags, STOP
You are about to violate the contract if any of these is true. Stop, do not
proceed.
- The thing has no static references, but the codebase uses reflection, DI, or
string dispatch. Keep it, flag it.
- Tests are red before you started and you moved on anyway. Stop, report.
- "It's obviously unused" without a whole-repo grep. Stop, grep first.
- You would touch an exported name or signature to make a split fit. Stop,
replan around a re-export.
- You are about to skip the post-change test run. Stop, run it.
Verification
Done means: tests green before and after, git diff scoped to internals only,
public names unchanged, and a one-line behavior-identical summary per change.
Run the type checker or linter if the project has one.
Tools
references/cleanup-safety.md - dynamic-reference and public-API detection
per language, test and rollback patterns.
scripts/find_oversized.py - files and units over a line threshold.
scripts/find_unused.py - definitions with no static references.
scripts/find_duplicates.py - repeated or near-repeated code blocks.
Boundaries
Out of scope. Route these elsewhere:
- New features and bug fixes. Behavior must change, the cleaner does not.
- Structural reorganization across files and directories. Use the manager skill.
- Performance work.
- Over-engineering review (ponytail's lane).
- Formatting and style. Use the project's linter or formatter.
1---2name: cleaner3description: Use when AI-generated code has bloated the repo: oversized files or classes, dead or unused code (functions, imports, variables, types, whole files), or duplicated blocks that need merging. Behavior-preserving cleanup only. Run when the user says "clean up this code", "remove dead code", "split this file", "this file is too big", "deduplicate", or "refactor without changing behavior". Not for new features, bug fixes, performance work, or over-engineering review.4---56# Cleaner78Behavior-preserving cleanup. Smaller files, no dead code, no duplication, same9behavior. The test suite ends green and the public surface is unchanged. If a10change would alter behavior, do not make it.1112## The Contract (non-negotiable)13141. **Tests first.** Run the project's tests before touching anything. Record15 green or red. If there is no test suite, say so and ask before proceeding.162. **One change at a time.** Apply one logical edit, re-run the tests, then17 commit or revert. On the first failure you did not cause, stop and revert.183. **Public surface frozen.** Exported names, function signatures, types, and19 module paths stay identical. Internal moves only.204. **No dynamic guesses.** Never delete code that may be reached by reflection,21 string dispatch, `eval`, dependency injection, or `__all__`-gated exports.22 When unsure, keep it and flag it for the human.235. **Every change gets a diff and a one-line reason.** Group changes into a24 reviewable set; nothing lands silently.2526## How it works2728Assess, propose, execute. Use the detection scripts to find candidates, rank29them by safety, then apply one at a time with verification.3031- **Assess.** Run `scripts/find_oversized.py`, `find_unused.py`, and32 `find_duplicates.py`, each with `--json`. Build a findings list.33- **Propose.** Rank by blast radius: pure-internal dead code first, then34 duplication, then file or class splits last. State the order out loud.35- **Execute.** Apply one change, run the tests, commit or revert, repeat.3637## Operations3839### Dead code4041Remove unused functions, imports, variables, types, and whole files. Confirm42zero references with a whole-repo grep, including inside strings and comments43(dynamic dispatch hides there). See `references/cleanup-safety.md` for the44per-language patterns that defeat a naive grep.4546### Split oversized files or classes4748When a file or class is too large, extract cohesive units into new modules.49Re-exports keep the public surface stable: the old path re-exports from the new50location so callers and imports do not change. Run the tests after every move.5152### Deduplicate5354When two or more blocks do the same thing, merge them into one shared helper.55The helper keeps the most general signature that preserves every existing call56site. Replace each duplicate with a call to the helper. Run the tests.5758## Red flags, STOP5960You are about to violate the contract if any of these is true. Stop, do not61proceed.6263- The thing has no static references, but the codebase uses reflection, DI, or64 string dispatch. **Keep it, flag it.**65- Tests are red before you started and you moved on anyway. **Stop, report.**66- "It's obviously unused" without a whole-repo grep. **Stop, grep first.**67- You would touch an exported name or signature to make a split fit. **Stop,68 replan around a re-export.**69- You are about to skip the post-change test run. **Stop, run it.**7071## Verification7273Done means: tests green before and after, `git diff` scoped to internals only,74public names unchanged, and a one-line behavior-identical summary per change.75Run the type checker or linter if the project has one.7677## Tools7879- `references/cleanup-safety.md` - dynamic-reference and public-API detection80 per language, test and rollback patterns.81- `scripts/find_oversized.py` - files and units over a line threshold.82- `scripts/find_unused.py` - definitions with no static references.83- `scripts/find_duplicates.py` - repeated or near-repeated code blocks.8485## Boundaries8687Out of scope. Route these elsewhere:8889- New features and bug fixes. Behavior must change, the cleaner does not.90- Structural reorganization across files and directories. Use the manager skill.91- Performance work.92- Over-engineering review (ponytail's lane).93- Formatting and style. Use the project's linter or formatter.