Refactor
Refactor code safely using characterization tests, incremental changes, and continuous verification. Every change preserves existing behavior while improving code structure, readability, and maintainability.
Refactoring Goal
$ARGUMENTS
Core Principle: Behavior Preservation
CRITICAL: Refactoring changes code structure WITHOUT changing behavior. Every step must be verified against existing tests. If tests break, the refactoring introduced a bug — revert and retry.
Anti-Hallucination Guidelines
- Read before changing — Never refactor code that has not been read and understood. Understand all callers and dependencies first.
- Test before and after — Run the full test suite before starting. Run it again after every incremental change. The results must match.
- Characterization tests first — If test coverage is insufficient for the target code, write characterization tests that capture current behavior BEFORE refactoring.
- Incremental changes — Make one small, verifiable change at a time. Never combine multiple refactoring steps into a single edit.
- No feature changes — Do not add features, fix bugs, or change behavior during refactoring. These are separate tasks.
- Reference real code — Never make claims about code structure that has not been verified by reading the actual files.
Quality Gates
This skill includes automatic refactoring verification before completion:
Safety Verification (Stop Hook)
When attempting to stop working, an automated verification agent runs to ensure the refactoring is safe:
Verification Steps:
- Behavior preserved: Full test suite passes (same results as before refactoring)
- No regressions: Linter and type checker report no new errors
- Characterization tests: Any tests added to capture behavior still pass
- Clean diff: Only intentional structural changes exist
Behavior:
- ✅ All verifications pass: Refactoring marked complete
- ❌ Any test fails: Completion blocked, Claude reverts or fixes the breaking change
- ⚠️ New lint/type errors: Completion blocked until resolved
Example blocked completion:
⚠️ Refactoring verification failed:
Tests: ❌ FAILED (2 tests failing)
- test_calculate_total: Expected 150.0, got 150
- test_format_output: AssertionError: output format changed
Lint: ✅ PASSED
Type Check: ✅ PASSED
🔧 Refactoring introduced behavior changes. Revert the last change and retry
with a smaller step.
Task Management
This skill uses Claude Code's Task Management System for strict sequential dependency tracking through the refactoring workflow.
When to Use Tasks:
- Multi-step refactoring across files or modules
- Extract method/class operations requiring dependency analysis
- Large-scale restructuring with many callers
- Work requiring progress tracking across sessions
When to Skip Tasks:
- Simple rename (single variable/function)
- Trivial formatting or style fix
- Single-line simplification
Task Structure: Refactoring creates a strict sequential chain where each phase must complete before the next can start, ensuring characterization tests exist before any structural changes begin.
Implementation Workflow
Task tracking replaces TodoWrite. Create task chain at start, update as completing each phase.
Phase 0: Project Discovery (REQUIRED)
Step 0.1: Create Task Dependency Chain
Before refactoring, create the strict sequential task structure:
TaskCreate:
subject: "Phase 0: Discover project workflow"
description: "Identify test, lint, type-check commands from CLAUDE.md and task runners"
activeForm: "Discovering project workflow"
TaskCreate:
subject: "Phase 1: Analyze refactoring scope"
description: "Map dependencies, callers, and test coverage for target code"
activeForm: "Analyzing refactoring scope"
TaskCreate:
subject: "Phase 2: Write characterization tests"
description: "Ensure sufficient test coverage before making structural changes"
activeForm: "Writing characterization tests"
TaskCreate:
subject: "Phase 3: Incremental refactoring"
description: "Apply refactoring in small verified steps"
activeForm: "Refactoring incrementally"
TaskCreate:
subject: "Phase 4: Final verification"
description: "Run full test suite, lint, type-check — confirm behavior preserved"
activeForm: "Verifying refactoring safety"
TaskCreate:
subject: "Phase 5: Final commit"
description: "Create conventional commit with refactoring summary"
activeForm: "Creating final commit"
# Set up strict sequential chain
TaskUpdate: { taskId: "2", addBlockedBy: ["1"] }
TaskUpdate: { taskId: "3", addBlockedBy: ["2"] }
TaskUpdate: { taskId: "4", addBlockedBy: ["3"] }
TaskUpdate: { taskId: "5", addBlockedBy: ["4"] }
TaskUpdate: { taskId: "6", addBlockedBy: ["5"] }
# Start first task
TaskUpdate: { taskId: "1", status: "in_progress" }
Step 0.2: Discover Project Workflow
Use Haiku-powered Explore agent for token-efficient discovery:
Use Task tool with Explore agent:
- prompt: "Discover the development workflow for this project:
1. Read CLAUDE.md if it exists - extract testing and quality conventions
2. Check for task runners: Makefile, justfile, package.json scripts, pyproject.toml scripts
3. Identify the test command (e.g., make test, just test, npm test, pytest, bun test)
4. Identify how to run a single test file or specific test
5. Identify the lint command (e.g., make lint, npm run lint, ruff check)
6. Identify the type-check command if applicable (e.g., pyright, tsc, mypy)
7. Note any pre-commit hooks or quality gates
8. Check for code coverage tooling (e.g., pytest --cov, nyc, c8)
Return a structured summary of all available commands."
- subagent_type: "Explore"
- model: "haiku" # Token-efficient for discovery
Store discovered commands for use in later phases.
Step 0.3: Run Baseline Tests
CRITICAL: Run the full test suite BEFORE making any changes. Record the results as the baseline. Every subsequent test run must match or exceed this baseline.
# Run full test suite and record results
[DISCOVERED_TEST_COMMAND]
If tests already fail before refactoring, document the pre-existing failures. These are not caused by the refactoring and should remain unchanged (same tests fail with same errors).
Step 0.4: Complete Phase 0
TaskUpdate: { taskId: "1", status: "completed" }
TaskList # Check that Task 2 is now unblocked
Phase 1: Scope Analysis
Goal: Understand the full impact of the refactoring before touching any code.
Step 1.1: Start Phase 1
TaskUpdate: { taskId: "2", status: "in_progress" }
Step 1.2: Parallel Scope Analysis
Spawn two Explore agents in parallel to map the refactoring scope:
Agent 1 - Dependency & Caller Analysis (Explore, Haiku):
prompt: "Analyze dependencies and callers for the refactoring target:
Refactoring target: [DESCRIBE TARGET CODE]
1. Read the target code — understand its current structure and public API
2. Find ALL callers and dependents using Grep:
- Direct function/method calls
- Import statements referencing the target
- Type references (if applicable)
- Configuration or dependency injection references
3. Map the dependency graph:
- What does the target depend on?
- What depends on the target?
- Are there circular dependencies?
4. Identify the public API surface:
- Which functions/methods/classes are called externally?
- Which are internal-only (safe to change freely)?
5. Note any dynamic references (string-based lookups, reflection, decorators)
Return:
- Complete caller list with file:line references
- Dependency graph (what depends on what)
- Public vs internal API surface
- Risks: dynamic references, external consumers, serialization"
subagent_type: "Explore"
model: "haiku"
Agent 2 - Test Coverage Analysis (Explore, Haiku):
prompt: "Analyze test coverage for the refactoring target:
Refactoring target: [DESCRIBE TARGET CODE]
1. Find ALL test files that test the target code:
- Unit tests directly testing target functions/classes
- Integration tests exercising the target indirectly
- E2E tests that flow through the target
2. For each test, note:
- What specific behavior it verifies
- Which code paths it exercises
- Which inputs and edge cases it covers
3. Identify GAPS in test coverage:
- Functions/methods with no tests
- Code branches not exercised (error paths, edge cases)
- Public API methods without direct test coverage
4. Check for test patterns:
- Test fixtures and helpers available
- Mocking patterns used in the project
- Test naming conventions
Return:
- List of test files with what they cover
- Coverage gaps requiring characterization tests
- Test patterns and fixtures available for reuse
- Risk areas: untested code paths that refactoring could break"
subagent_type: "Explore"
model: "haiku"
Step 1.3: Synthesize Analysis
After both agents complete, synthesize findings:
- Identify which parts of the target are safe to refactor (well-tested, internal-only)
- Identify which parts are risky (untested, public API, dynamic references)
- Determine if characterization tests are needed (Phase 2)
- Plan the order of incremental changes
Step 1.4: Get Approval for Large Refactorings
If the refactoring involves: changes to >5 files, modifications to public APIs, moving code between modules, or changes affecting external consumers, use AskUserQuestion to present the scope analysis and get approval before proceeding.
Step 1.5: Complete Phase 1
TaskUpdate: { taskId: "2", status: "completed" }
TaskList # Check that Task 3 is now unblocked
Phase 2: Characterization Tests
Goal: Ensure sufficient test coverage exists to detect any behavioral change from the refactoring.
Step 2.1: Start Phase 2
TaskUpdate: { taskId: "3", status: "in_progress" }
Step 2.2: Assess Coverage Needs
Based on Phase 1 coverage analysis, determine which characterization tests are needed:
- Well-tested code (>80% path coverage): Skip to Phase 3
- Partially tested (some gaps): Add targeted characterization tests for untested paths
- Poorly tested (major gaps): Add comprehensive characterization tests before proceeding
Step 2.3: Write Characterization Tests
For each coverage gap identified:
- Read the target code to understand current behavior (including edge cases)
- Write tests that capture current behavior exactly:
- Test the function/method with representative inputs
- Test edge cases (null, empty, boundary values)
- Test error paths (invalid inputs, failure modes)
- Test return values AND side effects
- Run the characterization tests — they must ALL pass against the current (pre-refactoring) code
- Name tests clearly: Use prefix
test_char_or equivalent to distinguish characterization tests from behavioral tests
Example characterization test (Python):
def test_char_calculate_total_with_discount():
"""Characterization: captures current discount calculation behavior."""
result = calculate_total(items=[100, 200], discount=0.1)
assert result == 270.0 # Current behavior: discount applied to sum
def test_char_calculate_total_empty_items():
"""Characterization: captures current behavior with empty input."""
result = calculate_total(items=[], discount=0.1)
assert result == 0.0
IMPORTANT: Characterization tests document CURRENT behavior, not desired behavior. If the current code has a quirk, the test captures that quirk. The refactoring must preserve it.
Step 2.4: Verify Characterization Tests
Run the full test suite (including new characterization tests). All must pass.
[DISCOVERED_TEST_COMMAND]
Step 2.5: Complete Phase 2
TaskUpdate: { taskId: "3", status: "completed" }
TaskList # Check that Task 4 is now unblocked
Phase 3: Incremental Refactoring
Goal: Apply the refactoring in small, verified steps. Each step must pass all tests.
Step 3.1: Start Phase 3
TaskUpdate: { taskId: "4", status: "in_progress" }
Step 3.2: Plan Incremental Steps
Break the refactoring into the smallest possible independent steps. Each step should be:
- Self-contained: Makes sense on its own
- Verifiable: Tests can confirm behavior is preserved
- Reversible: Easy to revert if something breaks
Common refactoring step sequences (see references/patterns.md for details):
| Refactoring Type | Step Sequence | |---|---| | Extract method | 1. Create new method with copied code → 2. Test → 3. Replace original with call → 4. Test | | Extract class | 1. Create class with methods → 2. Test → 3. Delegate from original → 4. Test → 5. Update callers → 6. Test | | Rename | 1. Add new name alongside old → 2. Test → 3. Update callers → 4. Test → 5. Remove old name → 6. Test | | Move function | 1. Copy to destination → 2. Test → 3. Re-export from source → 4. Update callers → 5. Test → 6. Remove source → 7. Test | | Simplify conditional | 1. Extract condition to named variable/method → 2. Test → 3. Simplify logic → 4. Test | | Remove duplication | 1. Identify shared pattern → 2. Extract shared code → 3. Test → 4. Replace first usage → 5. Test → 6. Replace next usage → 7. Test |
Step 3.3: Execute Each Step
For EACH incremental step:
- Make the change — one small structural change using Edit tool
- Run tests immediately — using discovered test command
[DISCOVERED_TEST_COMMAND] - If tests pass — proceed to next step
- If tests fail — STOP. Analyze the failure:
- Is it a behavioral change? → Revert and find a smaller step
- Is it a test that needs updating? → Only if the test was testing implementation details (not behavior)
- Is it a pre-existing failure? → Compare with baseline from Phase 0
CRITICAL: Never skip the test run between steps. Never batch multiple steps before testing. The discipline of test-after-every-change is what makes refactoring safe.
Step 3.4: Handle Complex Refactorings
For refactorings spanning multiple files:
- Update the target first — make the structural change in the primary file
- Update callers one at a time — change each caller individually, testing after each
- Clean up — remove old code, update imports, testing after each removal
For refactorings requiring temporary duplication:
- Create the new structure alongside the old
- Test — both old and new should work
- Migrate callers one at a time to the new structure, testing after each
- Remove the old structure once all callers are migrated
- Final test — full suite
Step 3.5: Complete Phase 3
TaskUpdate: { taskId: "4", status: "completed" }
TaskList # Check that Task 5 is now unblocked
Phase 4: Final Verification
Step 4.1: Start Phase 4
TaskUpdate: { taskId: "5", status: "in_progress" }
Step 4.2: Run All Quality Checks
Run all quality checks using discovered commands from Phase 0.
Quality Gates Checklist:
- [ ] Full test suite passes (matches or exceeds Phase 0 baseline)
- [ ] Characterization tests pass (behavior preserved)
- [ ] No new linting errors introduced
- [ ] No new type errors (if typed language)
- [ ] Code is cleaner/simpler than before (the purpose of refactoring)
- [ ] No accidental behavior changes
- [ ] No debug code, commented-out code, or TODO comments left behind
- [ ] All callers updated (no dangling references)
If any check fails: Fix the issue before proceeding. Do not commit broken code. Keep task as in_progress until all gates pass.
Step 4.3: Review the Diff
Read the complete diff to verify only intentional changes exist:
git diff
Look for:
- Unintended behavioral changes
- Leftover debug statements
- Unnecessary whitespace or formatting changes
- Missing import updates
- Orphaned code that should have been removed
Step 4.4: Complete Phase 4
TaskUpdate: { taskId: "5", status: "completed" }
TaskList # Check that Task 6 is now unblocked
Phase 5: Final Commit
Step 5.1: Start Phase 5
TaskUpdate: { taskId: "6", status: "in_progress" }
Step 5.2: Create Commit
If /cc-arsenal:git:commit skill is available, use it. Otherwise, create a conventional commit manually:
git add [files modified]
git commit -m "refactor: [concise description of structural change]
- [What was restructured and why]
- [Key technique used: extract method, rename, simplify, etc.]
- [Impact: files changed, callers updated]
No behavioral changes."
Commit message guidelines for refactoring:
- Type:
refactor:(always — this is a structural change, not a fix or feature) - Subject: Describe WHAT was restructured
- Body: Explain WHY this improves the codebase
- Footer: Always include "No behavioral changes." to signal safety
Step 5.3: Complete Phase 5 and Refactoring
TaskUpdate: { taskId: "6", status: "completed" }
TaskList # Show final status - all tasks should be completed
Phase 6: Summary Report
Report to the user with:
- Refactoring performed: What structural changes were made
- Technique used: Extract method, rename, simplify, move, etc.
- Files modified: List with brief description of changes
- Behavior verification: Test results before and after (matching)
- Characterization tests added: List of new tests if any
- Code quality improvements: Metrics if available (reduced duplication, fewer lines, better naming)
- Commit info: Commit hash and message
Additional Resources
For detailed refactoring patterns, step sequences, and safety practices, see:
- references/patterns.md - Refactoring catalog with step-by-step procedures
Important Notes
- Always run Phase 0 first: Never assume which tools are available
- Tests before and after every change: This is non-negotiable
- Characterization tests for untested code: Never refactor code without test coverage
- One step at a time: Never combine multiple refactoring operations
- No behavior changes: Refactoring changes structure only — if tests break, revert
- Ask when unsure: Better to clarify scope than to over-refactor
- Minimal scope: Refactor only what was requested — resist the urge to "improve" adjacent code
- Document the change: Clear commit message explaining what and why