| name | cleanup |
| description | Bounded, regression-safe code cleanup with deletion-first workflow. Use after a coding session that left dead code, duplicates, over-abstraction, or bloat. Also use when the user says "clean up", "simplify", or "remove cruft".
|
| user-invocable | true |
| allowed-tools | Agent Read Glob Grep Bash Edit Write |
/cleanup
Bounded, regression-safe code cleanup that removes AI-generated slop without
changing behavior. Deletion-first workflow: classify the mess, remove dead code,
consolidate duplicates, flatten needless abstractions — one pass at a time.
When to Use
- User says "cleanup", "clean up", "deslop", "anti-slop", "simplify", "too bloated"
- After a long coding session that produced working but messy code
- Code has duplicate logic, dead code, wrapper layers, over-abstraction
- User wants simplification without behavior changes
- Post-feature cleanup before committing
When NOT to Use
- Task is a new feature or product change — build it, don't clean it
- User wants a broad redesign — that's a feature, not cleanup
- Code is broken — use
/bugfix first, then cleanup
- The "mess" is just unfamiliar code style — don't clean what isn't dirty
Core Principles
- Preserve behavior — unless the user explicitly asks for behavior changes
- Deletion over addition — removing code is always safer than adding code
- One smell per pass — don't bundle unrelated refactors
- Reuse over reinvent — use existing utilities before creating new ones
- Small diffs — each change should be reversible and reviewable
- No scope creep — clean what was asked, resist "while I'm here" urges
Phase 1: Scope & Classify
-
Determine scope from {{ARGUMENTS}}:
- If specific files/paths given → scope to those files only
- If feature area named → find related files via Explore agent (model: haiku)
- If no scope given → scope to files changed in current git session (
git diff --name-only HEAD~10)
- Silent scope expansion is the primary way cleanup diffs become unreviable — keep scope exactly as requested
-
Read all files in scope — understand current state before touching anything
-
Classify the slop into 5 categories. For each, list specific instances with file:line:
| Category | What to Look For | Priority |
|---|
| Dead code | Unused imports, unreachable branches, commented-out code, stale feature flags, debug leftovers, unused variables/functions | 1 (safest to remove) |
| Duplication | Copy-paste logic, repeated patterns, redundant helpers doing the same thing | 2 |
| Needless abstraction | Pass-through wrappers, single-use helper layers, speculative indirection, "just in case" generalization | 3 |
| Boundary violations | Hidden coupling, wrong-layer imports, misplaced responsibilities, side effects in unexpected places | 4 |
| Naming & clarity | Misleading names, confusing parameter order, inconsistent conventions within the same module | 5 (lowest risk) |
Present the classification to the user:
## Cleanup Scope: {N} files
### Dead Code (Priority 1) — {count} instances
- `file.dart:42` — unused import `package:foo`
- `file.dart:89-102` — unreachable branch after early return
- `helper.dart` — entire file unused (0 references in lib/)
### Duplication (Priority 2) — {count} instances
- `screen_a.dart:30-45` and `screen_b.dart:22-37` — identical error handling
- `provider_a.dart:load()` and `provider_b.dart:fetch()` — same Supabase query pattern
### Needless Abstraction (Priority 3) — {count} instances
- `wrapper_service.dart` — passes through to repository with no added logic
### Boundary Violations (Priority 4) — {count} instances
- `widget.dart:15` — imports repository directly, bypassing provider
### Naming (Priority 5) — {count} instances
- `data` variable in `feed_provider.dart:67` — unclear what data it holds
Estimated cleanup: {small / medium / large}
Ask: "This is what I found. Should I clean all categories, specific ones, or adjust scope?"
Options:
- Clean all (Recommended) — "Run all passes in priority order"
- Dead code only — "Safest pass — just remove unused code"
- Dead code + duplication — "Remove unused code and consolidate duplicates"
- Let me pick — (free text)
Phase 2: Cleanup Passes
Run passes in priority order. Each pass is self-contained — verify after each one.
Pass 1: Dead Code Deletion
- Remove unused imports
- Remove unreachable branches
- Remove commented-out code (it's in git history if needed)
- Remove unused functions, classes, variables
- Remove stale feature flags and their branches
- Remove debug/print statements that should have been cleaned up
- Verification:
flutter analyze (or equivalent) — no new errors introduced
Pass 2: Duplicate Removal
- Identify the canonical version (usually the one with better error handling or naming)
- Replace duplicates with calls to the canonical version
- If no canonical version exists, extract a shared utility
- Place shared utilities in the appropriate layer (core/widgets, core/utils, etc.)
- Verification:
flutter analyze — no broken references
Pass 3: Flatten Abstractions
- Remove pass-through wrappers that add no logic
- Inline single-use helpers that obscure rather than clarify
- Collapse unnecessary inheritance hierarchies
- Remove "just in case" generalization that has exactly one usage
- Verification:
flutter analyze — no new errors
Pass 4: Fix Boundary Violations
- Move imports to the correct layer (widgets use providers, not repositories)
- Extract side effects from pure functions
- Move business logic out of UI widgets into providers/services
- Verification:
flutter analyze — no new errors
Pass 5: Naming & Clarity
- Rename misleading variables/functions to reflect actual purpose
- Fix inconsistent naming within a module (not across the whole project)
- Reorder parameters for consistency with similar functions
- Verification:
flutter analyze — no new errors
After EACH pass: run the project's analyzer/linter. The per-pass verification gate exists because cleanup changes compound — an unnoticed reference break in Pass 1 can look like a Pass 3 bug, making it much harder to isolate and revert. If the check fails, fix the issue or revert the risky change before continuing.
Phase 3: Report
After all passes complete:
## Cleanup Report
### Files Modified
- `file_a.dart` — removed 3 dead imports, inlined unused helper
- `file_b.dart` — consolidated duplicate error handler
- `wrapper_service.dart` — deleted (was a pass-through wrapper)
### Summary
| Category | Instances Found | Cleaned | Skipped (with reason) |
|----------|----------------|---------|----------------------|
| Dead code | 8 | 8 | 0 |
| Duplication | 3 | 2 | 1 (cross-module, needs broader refactor) |
| Abstraction | 2 | 2 | 0 |
| Boundary | 1 | 1 | 0 |
| Naming | 4 | 4 | 0 |
### Lines Removed: {net lines removed}
### Verification: flutter analyze — 0 issues
### Remaining Concerns
- {anything skipped and why}
- {anything that needs a broader refactor beyond cleanup scope}
Spawn doc-keeper in background if cleanup changed behavior documented in docs/.
Review Mode
If invoked with --review argument:
- Do NOT edit any files
- Read the files in scope
- Produce the classification report (Phase 1) only
- For each finding, state:
- What the slop is and where (file:line)
- Why it's slop (which category)
- What the fix would be (but don't apply it)
- Risk level of the fix (safe / needs-care / risky)
- End with a prioritized action list
This mode is for auditing code quality without making changes.
Scoped Mode
If invoked with specific file paths:
/cleanup lib/providers/quest_feed_provider.dart lib/features/quest_feed/
- Scope is EXACTLY those files/directories — no expansion
- Same pass-by-pass workflow, just narrower scope
- Useful after a focused feature session
Principles
Behavior preservation and scope discipline are already codified in the Core Principles above — they apply throughout every pass, not just as a checklist at the end.
Three constraints that prevent silent breakage:
- Before removing any code, verify it has no active references (Grep). Dead-looking code with live call sites is the most common cleanup regression.
- Run the analyzer/linter after each pass and don't continue if it's red. The per-pass gate is what makes each change independently reversible.
- Present the classification report before making any changes. The user may know about cross-module dependencies or intentional patterns that the classification can't infer.
A few more that aren't obvious from the pass structure: if a cleanup introduces a new error, revert that specific change — don't force it through by adding compensating edits. Commented-out code belongs in git history, not preserved in a "just in case" block. Empty catch blocks found during cleanup should get debug logging added per the error-handling rule, not silently removed — an empty catch almost always means something was swallowed intentionally, and removing it without logging loses that signal entirely.
Slop Smell Reference
Common AI-generated code smells to watch for:
| Smell | Example | Fix |
|---|
| Defensive overreach | Null checks on non-nullable types | Remove — the type system guarantees it |
| Wrapper theater | UserService that just calls UserRepository with zero added logic | Delete wrapper, use repository directly |
| Comment narration | // Get the user above getUser() | Remove — the code is self-documenting |
| Speculative generics | BaseProvider<T> used by exactly one provider | Inline — generalize when you have 3+ cases |
| Error message duplication | Same "Something went wrong" string in 10 files | Extract to shared constant or l10n key |
| Import hoarding | 15 imports, 6 unused | Remove unused imports |
| Dead parameter | Function accepts context but never uses it | Remove parameter, update call sites |
| Copy-paste divergence | Two functions that were copy-pasted and slightly modified | Extract shared logic, parameterize differences |
| Premature abstraction | WidgetFactory that builds exactly one widget type | Inline the widget directly |
| Log-and-throw | Catches error, logs it, throws a new error (loses stack trace) | Log and rethrow original, or handle without rethrowing |
Task: {{ARGUMENTS}}