From xpowers
Refactors code safely by making test-preserving transformations in small steps, running tests between each change to keep behavior unchanged.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xpowers:refactoring-safelyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<skill_overview>
<skill_overview> Refactoring changes code structure without changing behavior; tests must stay green throughout or you're rewriting, not refactoring. </skill_overview>
<rigidity_level> MEDIUM FREEDOM - Follow the change→test→commit cycle strictly, but adapt the specific refactoring patterns to your language and codebase. </rigidity_level>
<quick_reference>
| Step | Action | Verify |
|---|---|---|
| 0 | Confirm diagnosis + design exist | Required inputs present |
| 1 | Run full test suite | ALL pass |
| 2 | Create bd refactoring task | Track work |
| 3 | Make ONE small change | Compiles |
| 4 | Run tests immediately | ALL still pass |
| 5 | Commit with descriptive message | History clear |
| 6 | Repeat 3-5 until complete | Each step safe |
| 7 | Final verification & close bd | Done |
Core cycle: Change → Test → Commit (repeat until complete) </quick_reference>
<when_to_use>
Don't use for:
<the_process>
You must have:
refactoring-diagnosisrefactoring-designIf either is missing, stop and complete them first.
BEFORE any refactoring:
# Use test-runner agent to keep context clean
Dispatch xpowers:test-runner agent: "Run: cargo test"
Verify: ALL tests pass. If any fail, fix them FIRST, then refactor.
Why: Failing tests mean you can't detect if refactoring breaks things.
Track the refactoring work:
tm create "Refactor: Extract user validation logic" \
--type task \
--priority P2
tm update bd-456 --design "
## Goal
Extract user validation logic from UserService into separate Validator class.
## Why
- Validation duplicated across 3 services
- Makes testing individual validations difficult
- Violates single responsibility
## Approach
1. Create UserValidator class
2. Extract email validation
3. Extract name validation
4. Extract age validation
5. Update UserService to use validator
6. Remove duplication from other services
## Success Criteria
- All existing tests still pass
- No behavior changes
- Validator has 100% test coverage
"
tm update bd-456 --status in_progress
The smallest transformation that compiles.
Examples of "small":
NOT small:
Example:
// Before
fn create_user(name: &str, email: &str) -> Result<User> {
if email.is_empty() {
return Err(Error::InvalidEmail);
}
if !email.contains('@') {
return Err(Error::InvalidEmail);
}
let user = User { name, email };
Ok(user)
}
// After - ONE small change (extract email validation)
fn create_user(name: &str, email: &str) -> Result<User> {
validate_email(email)?;
let user = User { name, email };
Ok(user)
}
fn validate_email(email: &str) -> Result<()> {
if email.is_empty() {
return Err(Error::InvalidEmail);
}
if !email.contains('@') {
return Err(Error::InvalidEmail);
}
Ok(())
}
After EVERY small change:
Dispatch xpowers:test-runner agent: "Run: cargo test"
Verify: ALL tests still pass.
If tests fail:
git restore src/file.rsNever proceed with failing tests.
Commit each safe transformation:
Dispatch xpowers:test-runner agent: "Run: git add src/user_service.rs && git commit -m 'refactor(bd-456): extract email validation to function
No behavior change. All tests pass.
Part of bd-456'"
Why commit so often:
Repeat steps 3-5 for each small transformation:
1. Extract validate_email() ✓ (committed)
2. Extract validate_name() ✓ (committed)
3. Extract validate_age() ✓ (committed)
4. Create UserValidator struct ✓ (committed)
5. Move validations into UserValidator ✓ (committed)
6. Update UserService to use validator ✓ (committed)
7. Remove validation from OrderService ✓ (committed)
8. Remove validation from AccountService ✓ (committed)
Pattern: change → test → commit (repeat)
After all transformations complete:
# Full test suite
Dispatch xpowers:test-runner agent: "Run: cargo test"
# Linter
Dispatch xpowers:test-runner agent: "Run: cargo clippy"
Review the changes:
# See all refactoring commits
git log --oneline | grep "bd-456"
# Review full diff
git diff main...HEAD
Checklist:
Close bd task:
tm update bd-456 --design "
... (append to existing design)
## Completed
- Created UserValidator class with email, name, age validation
- Removed duplicated validation from 3 services
- All tests pass (verified)
- No behavior changes
- 8 small transformations, each tested
"
tm close bd-456
</the_process>
Developer changes behavior while "refactoring"
// Original code
fn validate_email(email: &str) -> Result<()> {
if email.is_empty() {
return Err(Error::InvalidEmail);
}
if !email.contains('@') {
return Err(Error::InvalidEmail);
}
Ok(())
}
// "Refactored" version
fn validate_email(email: &str) -> Result<()> {
if email.is_empty() {
return Err(Error::InvalidEmail);
}
if !email.contains('@') {
return Err(Error::InvalidEmail);
}
// NEW: Added extra validation
if !email.contains('.') { // BEHAVIOR CHANGE
return Err(Error::InvalidEmail);
}
Ok(())
}
<why_it_fails>
What you gain:
# Changes made all at once:
- Renamed 15 functions across 5 files
- Extracted 3 new classes
- Moved code between 10 files
- Reorganized module structure
- Updated all import statements
Then runs tests
$ cargo test
... 23 test failures ...
Now what? Which change broke what?
<why_it_fails>
If test fails:
What you gain:
// Legacy code with no tests
fn process_payment(amount: f64, user_id: i64) -> Result {
// 200 lines of complex payment logic
// Multiple edge cases
// No tests exist
}
// Developer refactors without tests:
// - Extracts 5 methods
// - Renames variables
// - Simplifies conditionals
// - "Looks good to me!"
// Deploys to production
// 💥 Payments fail for amounts over $1000
// Edge case handling was accidentally changed
<why_it_fails>
Write tests FIRST (using xpowers:test-driven-development)
Then refactor with tests as safety net
Tests catch any behavior changes immediately
What you gain:
<refactor_vs_rewrite>
Rule: If you need to change test assertions (not just add tests), you're rewriting, not refactoring.
When to use:
How it works:
Example:
Legacy: Monolithic user service (50K LOC)
Goal: Microservices architecture
Step 1 (Transform):
- Create new UserService microservice
- Implement user creation endpoint
- Tests pass in isolation
Step 2 (Coexist):
- Add routing layer (façade)
- Route POST /users to new service
- Route GET /users to legacy service (for now)
- Monitor both, compare results
Step 3 (Eliminate):
- Once confident, migrate GET /users to new service
- Remove user creation from legacy
- Repeat for remaining endpoints
Benefits:
Use refactoring within components, Strangler Fig for replacing systems. </refactor_vs_rewrite>
<critical_rules>
All of these mean: Stop and return to the change→test→commit cycle
<verification_checklist> Before marking refactoring complete:
Can't check all boxes? Return to process and fix before closing bd task. </verification_checklist>
**This skill requires:** - xpowers:test-driven-development (for writing tests before refactoring if none exist) - xpowers:verification-before-completion (for final verification) - xpowers:test-runner agent (for running tests without context pollution)This skill is called by:
Agents used:
When stuck:
npx claudepluginhub dpolishuk/xpowers --plugin xpowersRestructures existing code without changing behavior using behavior-locking tests and incremental verification after each move.
Refactors code safely by tracking progress in task docs and preserving behavior through small, test-verified changes. Use for deliberate, reversible refactors on painful designs.
Guides safe refactoring using Code Complete's fix-first-then-refactor discipline: decides between refactor, rewrite, and fix-first; enforces separate commits; applies small-change rigor.