| name | rewrite |
| description | This skill should be used when the user asks to "clean my code", "make it human readable", "make this readable", "rewrite this cleanly", "refactor this", "clean up this function", "simplify this code", "make this code better", "fix code quality", "clean this file", "this code is messy", "improve this code", or when code has visible violations like long functions, deep nesting, poor names, or missing interfaces. Produces a clean version preserving all behavior, with reasoning for each change. |
| argument-hint | [path, default: whole project] | paste code directly |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
| version | 0.2.0 |
Clean Code Rewrite
Produce a clean version of the provided code that preserves all existing behavior while fixing violations. Show what changed and why — this is both a fix and a learning moment.
Default Scope: whole codebase
No path given (and no code pasted) → scan the whole project, rank files by rewrite impact (size × violation density), and propose the single highest-impact file for rewrite. See ../../SCOPE_POLICY.md.
| Invocation | Behavior |
|---|
/cleancode:rewrite | Scan project, propose top candidate, confirm (default) |
/cleancode:rewrite <file> | Rewrite the named file |
/cleancode:rewrite <folder> | Rank files inside that folder, propose top candidate |
/cleancode:rewrite + pasted code block | Rewrite the pasted snippet |
/cleancode:rewrite . | Explicit whole-project (same as the no-arg default) |
Rewrite is a large, per-file operation — it always confirms the target file with the user before producing a full rewrite, even when the scope is the whole codebase. Only skip the ranking step when a path or pasted code is given in the command or in the current user message.
Core Principle
Behavior preservation first. A clean rewrite that changes what the code does is a bug introduction. Every rewrite must be functionally equivalent to the original.
Before Rewriting
- Read the full file or code — understand what it does completely before changing anything
- Identify all violations — mentally run the analyze checklist (file length, function length, naming, nesting, interfaces, DRY)
- Identify what NOT to change — public API signatures, exported names, database schemas, external interfaces
- Plan the split — if the file needs splitting, decide the new file names and what goes where
The Rewrite Process
Step 1: Fix the Biggest Violation First
Priority order:
- File over 300 lines → split the file. This is always done first.
- Functions over 40 lines → extract sub-functions
- Deep nesting (>4) → apply guard clauses
- Too many parameters (>4) → introduce a parameter interface
- Bad names → rename to reveal intent
- Missing interface [TS] → extract interface
- DRY violations → extract shared logic
- Comment cleanup → remove "what" comments, keep "why" comments
Step 2: Extract Functions with Good Names
When extracting a function:
- Name it by what it does, not how:
validateUserInput, not checkStuff
- The parent function should read like a table of contents after extraction
- Keep the new function in the same file or a clearly named new file
Step 3: Apply Guard Clauses
Transform nested conditionals into early returns:
if (x) {
if (y) {
// work
}
}
→
if (!x) return;
if (!y) return;
// work
Step 4: TypeScript — Extract Interfaces
For TypeScript files:
- If a class has no interface, extract one named
I[ClassName] or [ClassName] (team convention)
- If a function takes 4+ related parameters, create an interface for them
- Place interfaces at the top of the file or in a
types.ts alongside the implementation
Step 5: Fix Names
Rename in this priority:
- Generic names (
data, info, temp, obj, val, result) → specific nouns
- Abbreviated names → full words
- Non-verb function names → verb + noun
- Magic numbers → named constants
Output Format
Always show the rewrite as:
## Clean Rewrite: [filename or "provided code"]
### What Changed
| Before | After | Why |
|---|---|---|
| 83-line `handleOrder()` | 4 extracted functions | Do One Thing — each function has one purpose |
| Nested 5 levels deep | Guard clauses + early returns | Max nesting depth 4 |
| `data`, `obj`, `d` | `orderDetails`, `userAccount`, `date` | Intention-revealing names |
| No interface for `OrderService` | `IOrderService` interface extracted | Dependency Inversion |
### Clean Version
[full rewritten code here]
### Files Created (if split)
If the file was split:
- `[new-file-1.ts]` — [what it contains]
- `[new-file-2.ts]` — [what it contains]
### What Was Not Changed
[Note any public APIs, exports, or signatures preserved intentionally]
Handling File Splits
When a file over 300 lines needs splitting:
- Identify responsibilities — list each distinct concern in the file
- Name the new files by responsibility (not by technical layer when possible)
- Write all new files — don't leave partial rewrites
- Update imports — show the updated import statements in the parent
- State clearly which files to delete or replace
Example:
userService.ts (450 lines) → split into:
- auth/userAuthService.ts (validates, hashes, tokens)
- repositories/userRepository.ts (database access)
- email/userEmailService.ts (sends emails)
- formatting/userFormatter.ts (display utilities)
Each file: under 100 lines, single responsibility.
Rewrite Scope
- Small function (< 40 lines): rewrite inline, show before/after
- Single file (< 300 lines): rewrite the whole file
- Large file (> 300 lines): split into multiple files, show all of them
- Whole directory: ask user to confirm before proceeding — propose the split plan first
After the Rewrite
End every rewrite with:
Violations fixed: [N critical], [N warnings]
Files changed: [list]
Run /cleancode:analyze [file] to confirm 0 violations.
Run /cleancode:teach [principle] to understand any change made.
Constraints
- Never break the public API — if a function is exported or called externally, keep its signature
- Never change business logic — extract, rename, reorganize, but don't change behavior
- Ask before splitting across modules — if a split requires creating new directories or moving across package boundaries, confirm with the user first
- Preserve tests — if test files exist, note what test updates are needed but don't rewrite tests unless asked
Chain to /cleancode:restructure when a split crosses folder boundaries
/rewrite splits files into siblings in the same folder. When a split reveals that the extracted pieces actually belong in different folders per the project's conventions (e.g., extracting a service from a route handler — service belongs in services/ or lib/server/services/<feature>/, not next to the route file), do NOT silently apply the move yourself. Instead, after presenting the split plan:
-
Identify which extracted pieces are misplaced relative to the project's existing style (read top-level folders to infer style — feature-folders vs layered).
-
Append to your output:
## Cross-folder restructure suggested
This split would benefit from cross-folder relocation:
- <ExtractedPiece1> — better fits `<destination-folder>/`
- <ExtractedPiece2> — better fits `<other-destination>/`
Chain to /cleancode:restructure for an atomic, plan-first move with import rewrites? [y/N]
-
If the user confirms, hand off to /cleancode:restructure with the specific files and proposed destinations as input scope.
When NOT to chain: if all extracted files fit in the same folder as the original (typical case), no chain is needed. Only offer the chain when ≥ 2 extracted files clearly belong elsewhere — otherwise it's noise.
Additional Resources
Example Files
examples/before-after.md — 5 complete before/after rewrites covering the most common transformations: monolith function split, deep nesting fix, bad naming fix, missing interface extraction, and large file split. Reference these for realistic examples of each transformation type.