Refactoring Advisor
Systematic code smell detection and safe refactoring planning. Every refactoring plan ensures the codebase compiles and passes tests at every intermediate step.
Quick Start
Identify and Plan a Refactoring
- Detect the smell: What's wrong and why does it matter?
- Assess the risk: What could break? How large is the blast radius?
- Check test coverage: Are the affected areas covered? If not, add tests first.
- Plan incremental steps: Each step should leave the code in a working state.
- Estimate effort: Size the work (S/M/L/XL) and identify dependencies.
Common Code Smells by Language
Rust
| Smell |
Description |
Refactoring |
| God struct |
One struct with too many fields/methods |
Extract into smaller, focused structs |
| Unnecessary clones |
.clone() where borrowing would work |
Replace with borrows, use lifetimes |
| Stringly typed |
Using String where an enum fits |
Introduce enum with From impls |
| Match explosion |
Huge match blocks that grow with every variant |
Extract match arms into methods, use trait dispatch |
| Unwrap sprawl |
.unwrap() scattered through non-test code |
Replace with ? operator, proper error handling |
| Leaky abstraction |
pub on internal implementation details |
Reduce to pub(crate) or pub(super) |
Monolithic lib.rs |
Everything in one file |
Split into focused modules |
| Over-generic |
Generics where a concrete type suffices |
Simplify to concrete types until generics are needed |
TypeScript / React
| Smell |
Description |
Refactoring |
| Prop drilling |
Props passed through 3+ component levels |
Extract to context, composition, or custom hook |
| God component |
Component with 200+ lines or multiple responsibilities |
Split into focused components |
| Barrel file bloat |
Index files re-exporting everything |
Direct imports, remove barrel files |
| Any escape hatch |
as any or // @ts-ignore hiding type issues |
Fix the types, use type guards or discriminated unions |
| useEffect spaghetti |
Multiple unrelated effects in one component |
Split effects, extract to custom hooks |
| Inline styles sprawl |
Styles defined inline instead of using design system |
Extract to CSS modules, Tailwind classes, or styled components |
| Callback prop chains |
Passing callbacks through many layers |
Use context or state management |
| State duplication |
Same data stored in multiple state variables |
Derive computed values, single source of truth |
Django / Python
| Smell |
Description |
Refactoring |
| Fat views |
Business logic in views instead of services |
Extract to service layer |
| Circular imports |
Modules importing each other |
Restructure boundaries, use lazy imports |
| God model |
One model with 20+ fields and many methods |
Split into related models, use composition |
| N+1 queries |
Database queried in a loop |
Add select_related() / prefetch_related() |
| Magic strings |
Hardcoded strings for choices, status, etc. |
Use TextChoices / IntegerChoices enums |
| Untyped functions |
Missing type hints on public functions |
Add type annotations, run mypy |
| Test-free code |
No tests for critical business logic |
Add tests before refactoring |
| Signal spaghetti |
Complex logic hidden in Django signals |
Replace with explicit method calls |
Refactoring Safety Protocol
Before Any Refactoring
- Verify test coverage. Run existing tests. If the area being refactored has no tests, write characterization tests first.
- Create a checkpoint. Ensure all changes are committed. The refactoring starts from a clean state.
- Define the end state. What does "done" look like? How will you verify success?
During Refactoring
- One change at a time. Each commit should be a single, focused refactoring step.
- Compile after every change. (
cargo check, tsc --noEmit, python -m py_compile)
- Run tests after every change. If tests fail, fix before proceeding.
- No behavior changes. Refactoring changes structure, not behavior. If behavior must change, do it in a separate step.
After Refactoring
- Full test suite passes.
- Clippy / ESLint / Ruff clean. No new warnings introduced.
- Review the diff. Does the change accomplish what was intended? Any accidental behavior changes?
Refactoring Plan Template
When proposing a refactoring, use this structure:
## Refactoring Plan: [Description]
**Target:** [File/module/component]
**Smell:** [What's wrong]
**Goal:** [What "done" looks like]
**Risk:** Low / Medium / High
**Effort:** S / M / L / XL
**Test coverage:** Adequate / Needs improvement first
### Pre-requisites
- [ ] Tests pass on current code
- [ ] [Any additional pre-reqs]
### Steps
1. **[Step 1 description]**
- Files affected: [list]
- Verification: `cargo check && cargo test`
2. **[Step 2 description]**
- Files affected: [list]
- Verification: `cargo check && cargo test`
[... more steps ...]
### Rollback
If issues arise: `git revert` to the last passing commit.
### Success Criteria
- [ ] All tests pass
- [ ] No new clippy/lint warnings
- [ ] [Specific architectural improvement verified]
Migration Strategies
Pattern: Strangler Fig
For gradually replacing a legacy module:
- Create the new module alongside the old one
- Route new features through the new module
- Gradually migrate existing callers
- Remove the old module when all callers are migrated
Best for: Large-scale replacements where a clean cut is risky.
Pattern: Branch by Abstraction
For replacing an implementation behind an interface:
- Introduce an abstraction (trait/interface) over the current implementation
- Update callers to use the abstraction
- Implement the new version behind the same abstraction
- Switch the wiring (dependency injection, feature flag)
- Remove the old implementation
Best for: Swapping implementations (database, API client, algorithm).
Pattern: Parallel Change (Expand-Contract)
For changing a widely-used interface:
- Expand: Add the new interface alongside the old one
- Migrate: Update callers one by one to use the new interface
- Contract: Remove the old interface
Best for: API changes, function signature updates, data structure migrations.
Integration
- Data: Refactoring plans and outcomes logged to
MEMORY.md
- Tests: Always verify test coverage before and after. Use
cargo test -- --test-threads=1, bun test, or uv run pytest
- Tech debt: If a refactoring is deferred, add it to the tech debt registry in
MEMORY.md
Arc skill — Refactoring strategies and code smell detection
1---2name: refactoring-advisor3description: Agents should invoke this skill for refactors, code smells, migrations, duplication removal, module splitting, API cleanup, or restructuring plans. Emphasizes small safe steps, behavior preservation, and verification after each change.4---56# Refactoring Advisor78Systematic code smell detection and safe refactoring planning. Every refactoring plan ensures the codebase compiles and passes tests at every intermediate step.910## Quick Start1112### Identify and Plan a Refactoring13141. **Detect the smell:** What's wrong and why does it matter?152. **Assess the risk:** What could break? How large is the blast radius?163. **Check test coverage:** Are the affected areas covered? If not, add tests first.174. **Plan incremental steps:** Each step should leave the code in a working state.185. **Estimate effort:** Size the work (S/M/L/XL) and identify dependencies.1920---2122## Common Code Smells by Language2324### Rust2526| Smell | Description | Refactoring |27|---|---|---|28| God struct | One struct with too many fields/methods | Extract into smaller, focused structs |29| Unnecessary clones | `.clone()` where borrowing would work | Replace with borrows, use lifetimes |30| Stringly typed | Using `String` where an enum fits | Introduce enum with `From` impls |31| Match explosion | Huge `match` blocks that grow with every variant | Extract match arms into methods, use trait dispatch |32| Unwrap sprawl | `.unwrap()` scattered through non-test code | Replace with `?` operator, proper error handling |33| Leaky abstraction | `pub` on internal implementation details | Reduce to `pub(crate)` or `pub(super)` |34| Monolithic `lib.rs` | Everything in one file | Split into focused modules |35| Over-generic | Generics where a concrete type suffices | Simplify to concrete types until generics are needed |3637### TypeScript / React3839| Smell | Description | Refactoring |40|---|---|---|41| Prop drilling | Props passed through 3+ component levels | Extract to context, composition, or custom hook |42| God component | Component with 200+ lines or multiple responsibilities | Split into focused components |43| Barrel file bloat | Index files re-exporting everything | Direct imports, remove barrel files |44| Any escape hatch | `as any` or `// @ts-ignore` hiding type issues | Fix the types, use type guards or discriminated unions |45| useEffect spaghetti | Multiple unrelated effects in one component | Split effects, extract to custom hooks |46| Inline styles sprawl | Styles defined inline instead of using design system | Extract to CSS modules, Tailwind classes, or styled components |47| Callback prop chains | Passing callbacks through many layers | Use context or state management |48| State duplication | Same data stored in multiple state variables | Derive computed values, single source of truth |4950### Django / Python5152| Smell | Description | Refactoring |53|---|---|---|54| Fat views | Business logic in views instead of services | Extract to service layer |55| Circular imports | Modules importing each other | Restructure boundaries, use lazy imports |56| God model | One model with 20+ fields and many methods | Split into related models, use composition |57| N+1 queries | Database queried in a loop | Add `select_related()` / `prefetch_related()` |58| Magic strings | Hardcoded strings for choices, status, etc. | Use `TextChoices` / `IntegerChoices` enums |59| Untyped functions | Missing type hints on public functions | Add type annotations, run mypy |60| Test-free code | No tests for critical business logic | Add tests before refactoring |61| Signal spaghetti | Complex logic hidden in Django signals | Replace with explicit method calls |6263---6465## Refactoring Safety Protocol6667### Before Any Refactoring68691. **Verify test coverage.** Run existing tests. If the area being refactored has no tests, write characterization tests first.702. **Create a checkpoint.** Ensure all changes are committed. The refactoring starts from a clean state.713. **Define the end state.** What does "done" look like? How will you verify success?7273### During Refactoring74751. **One change at a time.** Each commit should be a single, focused refactoring step.762. **Compile after every change.** (`cargo check`, `tsc --noEmit`, `python -m py_compile`)773. **Run tests after every change.** If tests fail, fix before proceeding.784. **No behavior changes.** Refactoring changes structure, not behavior. If behavior must change, do it in a separate step.7980### After Refactoring81821. **Full test suite passes.**832. **Clippy / ESLint / Ruff clean.** No new warnings introduced.843. **Review the diff.** Does the change accomplish what was intended? Any accidental behavior changes?8586---8788## Refactoring Plan Template8990When proposing a refactoring, use this structure:9192```markdown93## Refactoring Plan: [Description]9495**Target:** [File/module/component]96**Smell:** [What's wrong]97**Goal:** [What "done" looks like]98**Risk:** Low / Medium / High99**Effort:** S / M / L / XL100**Test coverage:** Adequate / Needs improvement first101102### Pre-requisites103- [ ] Tests pass on current code104- [ ] [Any additional pre-reqs]105106### Steps1071081. **[Step 1 description]**109 - Files affected: [list]110 - Verification: `cargo check && cargo test`1111122. **[Step 2 description]**113 - Files affected: [list]114 - Verification: `cargo check && cargo test`115116[... more steps ...]117118### Rollback119If issues arise: `git revert` to the last passing commit.120121### Success Criteria122- [ ] All tests pass123- [ ] No new clippy/lint warnings124- [ ] [Specific architectural improvement verified]125```126127---128129## Migration Strategies130131### Pattern: Strangler Fig132133For gradually replacing a legacy module:1341351. Create the new module alongside the old one1362. Route new features through the new module1373. Gradually migrate existing callers1384. Remove the old module when all callers are migrated139140**Best for:** Large-scale replacements where a clean cut is risky.141142### Pattern: Branch by Abstraction143144For replacing an implementation behind an interface:1451461. Introduce an abstraction (trait/interface) over the current implementation1472. Update callers to use the abstraction1483. Implement the new version behind the same abstraction1494. Switch the wiring (dependency injection, feature flag)1505. Remove the old implementation151152**Best for:** Swapping implementations (database, API client, algorithm).153154### Pattern: Parallel Change (Expand-Contract)155156For changing a widely-used interface:1571581. **Expand:** Add the new interface alongside the old one1592. **Migrate:** Update callers one by one to use the new interface1603. **Contract:** Remove the old interface161162**Best for:** API changes, function signature updates, data structure migrations.163164---165166## Integration167168- **Data:** Refactoring plans and outcomes logged to `MEMORY.md`169- **Tests:** Always verify test coverage before and after. Use `cargo test -- --test-threads=1`, `bun test`, or `uv run pytest`170- **Tech debt:** If a refactoring is deferred, add it to the tech debt registry in `MEMORY.md`171172---173174_Arc skill — Refactoring strategies and code smell detection_