Clean Code Lifecycle
"Code is clean if it can be read, and enhanced by a developer other than its original author." — Grady Booch
When to Use
- Writing new code: To ensure high quality from the start.
- Reviewing Pull Requests: To provide constructive, principle-based feedback.
- Refactoring legacy code: To identify and remove code smells.
- Improving team standards: To align on industry-standard best practices.
Prerequisites
| Tool |
Purpose |
Install |
jscpd |
Multi-language clone detection |
npm install -g jscpd |
pmd |
Java/multi-language CPD |
pmd.github.io |
fd |
Fast file finder |
brew install fd / apt install fd-find |
rg |
Fast content search |
brew install ripgrep / apt install ripgrep |
golangci-lint |
Go meta-linter (50+ linters) |
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest |
clippy |
Rust idiomatic linter (500+ lints) |
rustup component add clippy |
biome |
Fast TS/JS linter + formatter |
bun install -D @biomejs/biome / npm install -D @biomejs/biome |
knip |
Find unused TS exports/deps/files |
bunx knip / npx knip |
ruff |
Python linter — unused imports/vars, dedup hints |
pipx install ruff |
vulture |
Python dead-code detection with confidence tiers |
pipx install vulture |
| Project linter |
Language-specific checks |
Check project config (.eslintrc, .golangci.yml, biome.json, pyproject.toml) |
Phase 0 — Project Context Discovery
Before applying any clean code principle, understand the project you're working in. Refactoring or deduplicating without context leads to wrong abstractions, broken conventions, and wasted effort.
Discovery Commands
# 1. Find project documentation — README, ADRs, contributing guides
fd -t f -i '(README|CONTRIBUTING|ADR|ARCHITECTURE|CONVENTIONS|STYLE_GUIDE)' .
# 2. Find configuration files that reveal conventions and tooling
fd -t f '(\.eslintrc|\.prettierrc|\.editorconfig|\.golangci|pyproject\.toml|biome\.json)' .
# 3. Check for a CLAUDE.md or similar AI-agent instructions
fd -t f 'CLAUDE.md' .
# 4. Read the project's commit style to match refactoring commits
git log --oneline -20
# 5. Check for existing shared utilities — avoid creating duplicates
fd -t f -i '(utils|helpers|shared|common|lib)' src/
Key Questions
| Question |
Why it matters |
Where to find it |
| Does the project have a style guide or coding conventions? |
Your refactoring must follow existing patterns, not introduce new ones |
CONTRIBUTING.md, linter configs, ADRs |
| Are there existing shared utility modules? |
Before extracting a helper, check if one already exists |
utils/, shared/, lib/, common/ dirs |
| What's the test strategy (unit, integration, e2e)? |
Determines how you verify refactoring safety |
README.md, CI config, test directory structure |
| Are there architectural boundaries (modules, packages, bounded contexts)? |
Deduplicating across boundaries may violate the architecture intentionally |
ARCHITECTURE.md, ADRs, module/package structure |
| Is there a dependency injection or service pattern in use? |
Extracting code the wrong way can break DI wiring |
Entry points, main files, DI containers |
Decision Rules
- If a style guide exists → follow it, even if it contradicts Clean Code principles. Project consistency wins over theoretical purity.
- If shared utils already exist → add to them instead of creating parallel helpers.
- If ADRs document a decision to keep duplication → respect it. Not all duplication is accidental.
- If no tests exist → write characterization tests before any refactoring (see Phase 3).
- If no documentation exists → read code structure, git history, and CI config to infer conventions.
Rule: context before cleanup. A "clean" refactoring that ignores project conventions creates more mess than the duplication it removed.
Phase 1 — Code Quality Audit
Scan the codebase for code smells. Each smell includes a description and detection method.
| # |
Smell |
Description |
Detection |
| 1 |
Rigidity |
One change forces a cascade of dependent changes |
Count how many files a single-line change touches |
| 2 |
Fragility |
Breaks in many places when you make a change |
Look for high coupling with no clear interface boundary |
| 3 |
Immobility |
Useful parts are entangled with unneeded details |
Functions that import half the project to do a simple task |
| 4 |
Viscosity |
Easier to hack than to follow the design |
Devs keep bypassing an abstraction — it's too cumbersome |
| 5 |
Needless Complexity |
Premature abstraction or speculative generality |
Unused interfaces, empty abstract methods, config nobody changes |
| 6 |
Needless Repetition |
Same logic in multiple places |
npx jscpd ./src or review similar function bodies |
| 7 |
Feature Envy |
A method accesses another object's data more than its own |
Chains: order.getCustomer().getAddress().getCity() |
| 8 |
Shotgun Surgery |
A single change requires edits across many files |
git log --name-only — same files always change together |
| 9 |
Divergent Change |
One class changed for many different reasons |
File with commits from unrelated features |
Principle Checks
For each file under review, verify against the core principles. See references/PRINCIPLES.md for full details.
- Names: Intention-revealing, searchable, pronounceable?
- Functions: Small (<30 lines), do one thing, ≤2 arguments?
- Comments: Can any comment be eliminated by making the code clearer?
- Formatting: Newspaper metaphor — high-level at top, details at bottom?
- Objects: Law of Demeter respected? No
a.getB().getC().doSomething()?
- Error Handling: Exceptions over return codes? No null returns/passes?
- Tests: F.I.R.S.T. principles followed?
- Classes: Single Responsibility Principle?
Language-Specific Checks
For language-specific smells, idioms, and detection commands:
- Go: See references/GOLANG.md — stuttering names, empty interface abuse,
init() side effects, naked returns, oversized interfaces, functional options
- Rust: See references/RUST.md —
unwrap() abuse, unnecessary clone(), stringly typed APIs, Arc<Mutex<>> overuse, monolithic error enums, boolean parameters
- TypeScript: See references/TYPESCRIPT.md —
any abuse, excessive type assertions, enum vs union, barrel file bloat, god interfaces, class overuse
- Bun: See references/BUN.md — Node.js APIs vs Bun natives, unnecessary polyfills,
dotenv/jest/express replacements, Bun.file/Bun.serve/Bun.password
- Python: See references/PYTHON.md — dict-as-object,
if/elif dispatch chains, **kwargs soup, boolean flag params, stateless classes, import-time side effects, sync/async twins
Phase 2 — Duplication & Dead Code Detection
Duplication is the same knowledge, logic, or intent expressed in more than one place. Dead code is a symbol nothing reaches. Both inflate the change surface — find them before Phase 3 touches anything.
2.1 Duplication
| Class |
Signature |
Cost of leaving it |
| Literal |
Identical blocks copied verbatim |
Copies drift; the next fix lands in one of them, not all |
| Logical |
Same outcome, different names or control flow |
Invisible to clone tools — only reading similar signatures finds it |
| Structural |
Repeated if/else or switch chains spelling out the same decision |
Adding one case means editing N sites; one gets forgotten |
| Data |
Constants, URLs, configs, error envelopes repeated across files |
Values drift silently; the copies disagree |
Detection commands per class, how to read the Structural signal from commit history, and the when NOT to deduplicate rules: references/DUPLICATION.md.
Rule of Three. Tolerate two copies. Extract on the third. A premature abstraction — the shared function that needs 4 parameters and 2 boolean flags to serve every caller — is worse than the duplication it removed.
2.2 Dead Code
| Type |
Example |
| Unreferenced function / type |
No call site in source, tests, templates, config, or CI |
| Unused import |
Flagged by the linter, with no side-effect or re-export role |
| Assigned-never-read variable / unused parameter |
Flagged by the linter, not fixed by an interface signature |
| Unreachable branch |
Dead feature flag, code after return, provably constant guard |
| Unused dependency / orphan file |
Manifest or module graph shows zero importers |
Command matrix per language, plus the twelve false-positive guardrails that must clear before any deletion: references/DEAD_CODE.md.
Rule: two independent signals before deleting. A tool finding plus a manual sweep of non-source assets. Reflection, DI registries, framework decorators, serialization, and string-based routing are invisible to every dead-code tool — deleting a live symbol passes the test suite and breaks production.
Phase 3 — Safe Refactoring
Apply refactoring patterns to resolve the issues found in Phases 1 and 2. For concrete before/after diffs, see references/PATTERNS.md.
Available Patterns
| Pattern |
Use When |
Result |
| Extract Function |
Identical blocks across multiple call sites |
Auth check in every handler → middleware |
| Extract Constant/Config |
Magic values repeated across files |
30 * time.Second in 3 files → config.DefaultTimeout |
| Generic/Parameterized Function |
Near-identical functions differing by one call |
GetUser, GetOrder → getByID[T] |
| Template Method / Strategy |
Similar flows with one varying step |
PDF/CSV generators → GenerateReport(data, renderer) |
| Substitute Algorithm |
Two functions reach the same result by different means |
Two CSV parsers → keep the streaming one, delete the other |
| Replace Conditional with Polymorphism |
The same switch/if-elif chain repeated across files |
Channel switch in 3 handlers → dispatch table, then interface |
Step 1 — Secure the starting point
# Ensure all tests pass BEFORE you start
go test ./... # Go
cargo test # Rust
bun test # Bun
npm test # Node/TS
pytest # Python
# Ensure a clean git state
git status # should be clean, or stash first
git stash # if needed
# Create a dedicated branch
git checkout -b refactor/describe-the-change
Rule: never refactor on a dirty working tree. Mixing feature changes with refactoring makes rollback impossible.
Step 2 — One transformation at a time
Each refactoring step must be atomic — a single, small, independently verifiable change.
| Step |
Action |
Verify |
| 1 |
Extract function / constant / type |
Run tests |
| 2 |
Replace first call site with the new abstraction |
Run tests |
| 3 |
Replace next call site |
Run tests |
| 4 |
Remove old dead code — follow the DEAD_CODE.md protocol: two signals, guardrails cleared, own commit |
Run tests |
| 5 |
Commit |
git commit -m "refactor: extract getByID generic handler" |
Never batch multiple extractions into a single step.
# After EACH small change:
go test ./... # or your project's test command
git add -p # stage only the relevant change
git commit -m "refactor: step N — description"
Step 3 — Verify behavior preservation
# Go: check exported symbols haven't changed
go doc ./pkg/handlers
go vet ./...
golangci-lint run ./...
# Rust: clippy + format + test
cargo clippy -- -W clippy::pedantic
cargo fmt -- --check
cargo test
# TypeScript / Bun: type check + lint
bunx tsc --noEmit # or: npx tsc --noEmit
bunx biome check . # or: npx eslint .
bun test # or: npx vitest run
# Python: type check + lint + test
ruff check .
ruff format --check .
mypy --strict .
pytest -q
# Find unused exports and dependencies (TypeScript / Bun)
bunx knip
# Run integration/e2e tests if available
npm run test:e2e
# Check for unused imports/variables introduced by refactoring
go vet ./... # Go
cargo machete # Rust
bunx knip # TypeScript / Bun
npx eslint --rule '{"no-unused-vars": "error"}' src/ # TypeScript (eslint)
ruff check --select=F401,F841,ARG . # Python — unused imports/vars/params
vulture src/ tests/ --min-confidence 90 # Python — dead symbols
Step 4 — Rollback strategy
# Undo current uncommitted change (keep committed steps)
git checkout -- .
# Revert just one committed step
git revert <commit-hash>
# Abandon the entire refactoring branch
git checkout main
git branch -D refactor/describe-the-change
The branch-per-refactoring approach means you never risk main.
Common Pitfalls
- Changing behavior during refactoring: Resist the urge to "fix that bug while I'm here." Refactoring and behavior changes are separate commits — always.
- Refactoring without tests: If the code has no tests, write characterization tests first — tests that capture current behavior, even if that behavior has bugs.
- Big-bang refactoring: Rewriting an entire module at once. Prefer the Strangler Fig pattern — replace piece by piece.
- Skipping the test run: "It's just a rename." Type aliases, reflection, serialization, string-based routing — all break on renames.
Plan-only mode
Composing /clean-code /only-plan suppresses every write in Phase 3 — no branch, no commits, no edits. Phases 0–2 run normally, and each planned transformation becomes a numbered step in the single IMPLEMENTATION_PLAN.md at the project root, following @only-plan's section contract. This skill writes no file of its own in either mode.
Related Skills
@only-plan — compose as /clean-code /only-plan for a refactor plan at the project root instead of applied edits.
@ponytail-review — the complementary lens: what to delete (speculative abstractions, dependencies the stdlib replaces, config nobody sets) rather than what to restructure.
@code-optimization — when duplication or a smell has a measurable performance cost; it grades Impact and writes OPTIMIZATION_REPORT.md.
@code-review — broad severity-ranked review; its Rule-of-Three findings hand off here for the refactor mechanics.
@code-debugger — a smell that is actually a live defect belongs there first. Refactor after the fix is green.
References
- PRINCIPLES — the eight Clean Code principle families
- PATTERNS — six refactoring patterns with before/after diffs
- DUPLICATION — taxonomy, detection probes, when NOT to deduplicate
- DEAD_CODE — per-language detection, false-positive guardrails, removal protocol
- GOLANG · RUST · TYPESCRIPT · BUN · PYTHON — language-specific smells and idioms
Implementation Checklist
1---2name: clean-code3description: Clean code lifecycle for Go/Rust/TypeScript/Bun/Python — writing, reviewing, refactoring. Naming, functions, DRY, code smells, duplication (literal/logical/structural), dead-code removal with false-positive guardrails, safe refactoring. Triggers: 'clean code', 'código limpo', 'refactor', 'refatorar', 'remove duplication', 'remover duplicação', 'dead code', 'código morto', 'code smells', '/clean-code'.4---56# Clean Code Lifecycle78> "Code is clean if it can be read, and enhanced by a developer other than its original author." — Grady Booch910## When to Use1112- **Writing new code**: To ensure high quality from the start.13- **Reviewing Pull Requests**: To provide constructive, principle-based feedback.14- **Refactoring legacy code**: To identify and remove code smells.15- **Improving team standards**: To align on industry-standard best practices.1617## Prerequisites1819| Tool | Purpose | Install |20|------|---------|---------|21| `jscpd` | Multi-language clone detection | `npm install -g jscpd` |22| `pmd` | Java/multi-language CPD | [pmd.github.io](https://pmd.github.io/) |23| `fd` | Fast file finder | `brew install fd` / `apt install fd-find` |24| `rg` | Fast content search | `brew install ripgrep` / `apt install ripgrep` |25| `golangci-lint` | Go meta-linter (50+ linters) | `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest` |26| `clippy` | Rust idiomatic linter (500+ lints) | `rustup component add clippy` |27| `biome` | Fast TS/JS linter + formatter | `bun install -D @biomejs/biome` / `npm install -D @biomejs/biome` |28| `knip` | Find unused TS exports/deps/files | `bunx knip` / `npx knip` |29| `ruff` | Python linter — unused imports/vars, dedup hints | `pipx install ruff` |30| `vulture` | Python dead-code detection with confidence tiers | `pipx install vulture` |31| Project linter | Language-specific checks | Check project config (`.eslintrc`, `.golangci.yml`, `biome.json`, `pyproject.toml`) |3233## Phase 0 — Project Context Discovery3435Before applying any clean code principle, **understand the project you're working in.** Refactoring or deduplicating without context leads to wrong abstractions, broken conventions, and wasted effort.3637### Discovery Commands3839```bash40# 1. Find project documentation — README, ADRs, contributing guides41fd -t f -i '(README|CONTRIBUTING|ADR|ARCHITECTURE|CONVENTIONS|STYLE_GUIDE)' .4243# 2. Find configuration files that reveal conventions and tooling44fd -t f '(\.eslintrc|\.prettierrc|\.editorconfig|\.golangci|pyproject\.toml|biome\.json)' .4546# 3. Check for a CLAUDE.md or similar AI-agent instructions47fd -t f 'CLAUDE.md' .4849# 4. Read the project's commit style to match refactoring commits50git log --oneline -205152# 5. Check for existing shared utilities — avoid creating duplicates53fd -t f -i '(utils|helpers|shared|common|lib)' src/54```5556### Key Questions5758| Question | Why it matters | Where to find it |59|----------|---------------|------------------|60| Does the project have a style guide or coding conventions? | Your refactoring must follow existing patterns, not introduce new ones | `CONTRIBUTING.md`, linter configs, ADRs |61| Are there existing shared utility modules? | Before extracting a helper, check if one already exists | `utils/`, `shared/`, `lib/`, `common/` dirs |62| What's the test strategy (unit, integration, e2e)? | Determines how you verify refactoring safety | `README.md`, CI config, test directory structure |63| Are there architectural boundaries (modules, packages, bounded contexts)? | Deduplicating across boundaries may violate the architecture intentionally | `ARCHITECTURE.md`, ADRs, module/package structure |64| Is there a dependency injection or service pattern in use? | Extracting code the wrong way can break DI wiring | Entry points, main files, DI containers |6566### Decision Rules6768- **If a style guide exists** → follow it, even if it contradicts Clean Code principles. Project consistency wins over theoretical purity.69- **If shared utils already exist** → add to them instead of creating parallel helpers.70- **If ADRs document a decision to keep duplication** → respect it. Not all duplication is accidental.71- **If no tests exist** → write characterization tests before any refactoring (see Phase 3).72- **If no documentation exists** → read code structure, git history, and CI config to infer conventions.7374> **Rule: context before cleanup.** A "clean" refactoring that ignores project conventions creates more mess than the duplication it removed.7576## Phase 1 — Code Quality Audit7778Scan the codebase for code smells. Each smell includes a description and detection method.7980| # | Smell | Description | Detection |81|---|-------|-------------|-----------|82| 1 | **Rigidity** | One change forces a cascade of dependent changes | Count how many files a single-line change touches |83| 2 | **Fragility** | Breaks in many places when you make a change | Look for high coupling with no clear interface boundary |84| 3 | **Immobility** | Useful parts are entangled with unneeded details | Functions that import half the project to do a simple task |85| 4 | **Viscosity** | Easier to hack than to follow the design | Devs keep bypassing an abstraction — it's too cumbersome |86| 5 | **Needless Complexity** | Premature abstraction or speculative generality | Unused interfaces, empty abstract methods, config nobody changes |87| 6 | **Needless Repetition** | Same logic in multiple places | `npx jscpd ./src` or review similar function bodies |88| 7 | **Feature Envy** | A method accesses another object's data more than its own | Chains: `order.getCustomer().getAddress().getCity()` |89| 8 | **Shotgun Surgery** | A single change requires edits across many files | `git log --name-only` — same files always change together |90| 9 | **Divergent Change** | One class changed for many different reasons | File with commits from unrelated features |9192### Principle Checks9394For each file under review, verify against the core principles. See [references/PRINCIPLES.md](references/PRINCIPLES.md) for full details.9596- **Names**: Intention-revealing, searchable, pronounceable?97- **Functions**: Small (<30 lines), do one thing, ≤2 arguments?98- **Comments**: Can any comment be eliminated by making the code clearer?99- **Formatting**: Newspaper metaphor — high-level at top, details at bottom?100- **Objects**: Law of Demeter respected? No `a.getB().getC().doSomething()`?101- **Error Handling**: Exceptions over return codes? No null returns/passes?102- **Tests**: F.I.R.S.T. principles followed?103- **Classes**: Single Responsibility Principle?104105### Language-Specific Checks106107For language-specific smells, idioms, and detection commands:108109- **Go**: See [references/GOLANG.md](references/GOLANG.md) — stuttering names, empty interface abuse, `init()` side effects, naked returns, oversized interfaces, functional options110- **Rust**: See [references/RUST.md](references/RUST.md) — `unwrap()` abuse, unnecessary `clone()`, stringly typed APIs, `Arc<Mutex<>>` overuse, monolithic error enums, boolean parameters111- **TypeScript**: See [references/TYPESCRIPT.md](references/TYPESCRIPT.md) — `any` abuse, excessive type assertions, enum vs union, barrel file bloat, god interfaces, class overuse112- **Bun**: See [references/BUN.md](references/BUN.md) — Node.js APIs vs Bun natives, unnecessary polyfills, `dotenv`/`jest`/`express` replacements, `Bun.file`/`Bun.serve`/`Bun.password`113- **Python**: See [references/PYTHON.md](references/PYTHON.md) — dict-as-object, `if/elif` dispatch chains, `**kwargs` soup, boolean flag params, stateless classes, import-time side effects, sync/async twins114115## Phase 2 — Duplication & Dead Code Detection116117Duplication is the same knowledge, logic, or intent expressed in more than one place. Dead code is a symbol nothing reaches. Both inflate the change surface — find them before Phase 3 touches anything.118119### 2.1 Duplication120121| Class | Signature | Cost of leaving it |122|-------|-----------|--------------------|123| **Literal** | Identical blocks copied verbatim | Copies drift; the next fix lands in one of them, not all |124| **Logical** | Same outcome, different names or control flow | Invisible to clone tools — only reading similar signatures finds it |125| **Structural** | Repeated `if/else` or `switch` chains spelling out the same decision | Adding one case means editing N sites; one gets forgotten |126| **Data** | Constants, URLs, configs, error envelopes repeated across files | Values drift silently; the copies disagree |127128Detection commands per class, how to read the Structural signal from commit history, and the **when NOT to deduplicate** rules: [references/DUPLICATION.md](references/DUPLICATION.md).129130> **Rule of Three.** Tolerate two copies. Extract on the third. A premature abstraction — the shared function that needs 4 parameters and 2 boolean flags to serve every caller — is worse than the duplication it removed.131132### 2.2 Dead Code133134| Type | Example |135|------|---------|136| **Unreferenced function / type** | No call site in source, tests, templates, config, or CI |137| **Unused import** | Flagged by the linter, with no side-effect or re-export role |138| **Assigned-never-read variable / unused parameter** | Flagged by the linter, not fixed by an interface signature |139| **Unreachable branch** | Dead feature flag, code after `return`, provably constant guard |140| **Unused dependency / orphan file** | Manifest or module graph shows zero importers |141142Command matrix per language, plus the twelve false-positive guardrails that must clear before any deletion: [references/DEAD_CODE.md](references/DEAD_CODE.md).143144> **Rule: two independent signals before deleting.** A tool finding plus a manual sweep of non-source assets. Reflection, DI registries, framework decorators, serialization, and string-based routing are invisible to every dead-code tool — deleting a live symbol passes the test suite and breaks production.145146## Phase 3 — Safe Refactoring147148Apply refactoring patterns to resolve the issues found in Phases 1 and 2. For concrete before/after diffs, see [references/PATTERNS.md](references/PATTERNS.md).149150### Available Patterns151152| Pattern | Use When | Result |153|---------|----------|--------|154| **Extract Function** | Identical blocks across multiple call sites | Auth check in every handler → middleware |155| **Extract Constant/Config** | Magic values repeated across files | `30 * time.Second` in 3 files → `config.DefaultTimeout` |156| **Generic/Parameterized Function** | Near-identical functions differing by one call | `GetUser`, `GetOrder` → `getByID[T]` |157| **Template Method / Strategy** | Similar flows with one varying step | PDF/CSV generators → `GenerateReport(data, renderer)` |158| **Substitute Algorithm** | Two functions reach the same result by different means | Two CSV parsers → keep the streaming one, delete the other |159| **Replace Conditional with Polymorphism** | The same `switch`/`if-elif` chain repeated across files | Channel switch in 3 handlers → dispatch table, then interface |160161### Step 1 — Secure the starting point162163```bash164# Ensure all tests pass BEFORE you start165go test ./... # Go166cargo test # Rust167bun test # Bun168npm test # Node/TS169pytest # Python170171# Ensure a clean git state172git status # should be clean, or stash first173git stash # if needed174175# Create a dedicated branch176git checkout -b refactor/describe-the-change177```178179**Rule: never refactor on a dirty working tree.** Mixing feature changes with refactoring makes rollback impossible.180181### Step 2 — One transformation at a time182183Each refactoring step must be **atomic** — a single, small, independently verifiable change.184185| Step | Action | Verify |186|------|--------|--------|187| 1 | Extract function / constant / type | Run tests |188| 2 | Replace first call site with the new abstraction | Run tests |189| 3 | Replace next call site | Run tests |190| 4 | Remove old dead code — follow the [DEAD_CODE.md](references/DEAD_CODE.md) protocol: two signals, guardrails cleared, own commit | Run tests |191| 5 | Commit | `git commit -m "refactor: extract getByID generic handler"` |192193**Never batch multiple extractions into a single step.**194195```bash196# After EACH small change:197go test ./... # or your project's test command198git add -p # stage only the relevant change199git commit -m "refactor: step N — description"200```201202### Step 3 — Verify behavior preservation203204```bash205# Go: check exported symbols haven't changed206go doc ./pkg/handlers207go vet ./...208golangci-lint run ./...209210# Rust: clippy + format + test211cargo clippy -- -W clippy::pedantic212cargo fmt -- --check213cargo test214215# TypeScript / Bun: type check + lint216bunx tsc --noEmit # or: npx tsc --noEmit217bunx biome check . # or: npx eslint .218bun test # or: npx vitest run219220# Python: type check + lint + test221ruff check .222ruff format --check .223mypy --strict .224pytest -q225226# Find unused exports and dependencies (TypeScript / Bun)227bunx knip228229# Run integration/e2e tests if available230npm run test:e2e231232# Check for unused imports/variables introduced by refactoring233go vet ./... # Go234cargo machete # Rust235bunx knip # TypeScript / Bun236npx eslint --rule '{"no-unused-vars": "error"}' src/ # TypeScript (eslint)237ruff check --select=F401,F841,ARG . # Python — unused imports/vars/params238vulture src/ tests/ --min-confidence 90 # Python — dead symbols239```240241### Step 4 — Rollback strategy242243```bash244# Undo current uncommitted change (keep committed steps)245git checkout -- .246247# Revert just one committed step248git revert <commit-hash>249250# Abandon the entire refactoring branch251git checkout main252git branch -D refactor/describe-the-change253```254255The branch-per-refactoring approach means you never risk `main`.256257### Common Pitfalls258259- **Changing behavior during refactoring**: Resist the urge to "fix that bug while I'm here." Refactoring and behavior changes are separate commits — always.260- **Refactoring without tests**: If the code has no tests, **write characterization tests first** — tests that capture current behavior, even if that behavior has bugs.261- **Big-bang refactoring**: Rewriting an entire module at once. Prefer the Strangler Fig pattern — replace piece by piece.262- **Skipping the test run**: "It's just a rename." Type aliases, reflection, serialization, string-based routing — all break on renames.263264### Plan-only mode265266Composing `/clean-code /only-plan` suppresses every write in Phase 3 — no branch, no commits, no edits. Phases 0–2 run normally, and each planned transformation becomes a numbered step in the single `IMPLEMENTATION_PLAN.md` at the project root, following `@only-plan`'s section contract. This skill writes no file of its own in either mode.267268## Related Skills269270- `@only-plan` — compose as `/clean-code /only-plan` for a refactor plan at the project root instead of applied edits.271- `@ponytail-review` — the complementary lens: what to **delete** (speculative abstractions, dependencies the stdlib replaces, config nobody sets) rather than what to restructure.272- `@code-optimization` — when duplication or a smell has a measurable performance cost; it grades Impact and writes `OPTIMIZATION_REPORT.md`.273- `@code-review` — broad severity-ranked review; its Rule-of-Three findings hand off here for the refactor mechanics.274- `@code-debugger` — a smell that is actually a live defect belongs there first. Refactor after the fix is green.275276## References277278- [PRINCIPLES](references/PRINCIPLES.md) — the eight Clean Code principle families279- [PATTERNS](references/PATTERNS.md) — six refactoring patterns with before/after diffs280- [DUPLICATION](references/DUPLICATION.md) — taxonomy, detection probes, when NOT to deduplicate281- [DEAD_CODE](references/DEAD_CODE.md) — per-language detection, false-positive guardrails, removal protocol282- [GOLANG](references/GOLANG.md) · [RUST](references/RUST.md) · [TYPESCRIPT](references/TYPESCRIPT.md) · [BUN](references/BUN.md) · [PYTHON](references/PYTHON.md) — language-specific smells and idioms283284## Implementation Checklist285286- [ ] Is this function smaller than 30 lines?287- [ ] Does this function do exactly one thing?288- [ ] Are all names searchable and intention-revealing?289- [ ] Have I avoided comments by making the code clearer?290- [ ] Am I passing too many arguments?291- [ ] Is there a failing test for this change?292- [ ] Is there duplicated logic that could be extracted into a shared function?293- [ ] Are magic strings/numbers extracted into named constants?294- [ ] Did I check for existing utilities before writing a new helper?295- [ ] If I extracted a shared abstraction, is it used in 3+ places (Rule of Three)?296- [ ] Did two independent signals confirm each dead symbol before deleting it (tool finding + non-source sweep)?297- [ ] Did I clear the false-positive guardrails (reflection, DI, framework decorators, serialization, entry points, published API)?298- [ ] Is each deletion in its own commit, separate from any refactor?299- [ ] Did I run all tests before AND after refactoring?300- [ ] Is each refactoring step in its own commit (one transformation per commit)?301- [ ] Did I avoid mixing behavior changes with structural refactoring?302- [ ] If the code had no tests, did I write characterization tests before refactoring?