Multi-File Refactoring with AI Agents
Large-scale refactoring with AI agents has hidden costs. Learn to manage context overflow, minimize verification tax, and maintain consistency.
Large-scale refactoring with AI agents like Claude Code and Cursor is powerful but comes with hidden costs. Context overflow causes hallucinations, verification requires careful review, and the "verification tax" can outweigh the speed benefits if not managed properly.
The Problem: Scaling AI Refactoring
AI agents excel at small, focused changes but struggle with:
- Context overflow: Large codebases exceed context windows
- Verification tax: Reviewing AI-generated changes takes time
- Hallucinations: Agents invent non-existent patterns
- Inconsistency: Multiple files get different approaches
Pattern 1: Incremental Refactoring
Break large refactors into small, focused steps:
# ❌ Bad: One massive refactor
agent refactor "add TypeScript strict mode to entire codebase"
# ✅ Good: Incremental approach
agent refactor "add strict mode to lib/ directory"
agent refactor "add strict mode to components/ directory"
agent refactor "add strict mode to app/ directory"
agent refactor "add strict mode to pages/ directory"Each step is reviewed before moving to the next:
# Step 1: Update tsconfig.json
agent edit "enable strict mode" tsconfig.json
# Review changes...
git diff
# Step 2: Fix errors in lib/
agent fix "fix lib/ TypeScript errors" src/lib/*.ts
# Review and commit...
git commit -m "Enable strict mode in lib/"
# Step 3: Fix errors in components/
agent fix "fix components TypeScript errors" src/components/*.tsx
# Review and commit...
git commit -m "Enable strict mode in components/"
# Repeat for each directory...Pattern 2: Context Architecture
Structure context for effective refactoring:
# REFACTOR_PLAN.md
## Goal: Add TypeScript strict mode
## Scope
- lib/ directory (utility functions)
- components/ directory (React components)
- app/ directory (Next.js pages)
## Approach
1. Update tsconfig.json with strict: true
2. Fix errors incrementally by directory
3. Add type annotations where needed
4. Update tests
## Files
- tsconfig.json
- src/lib/*.ts (12 files)
- src/components/*.tsx (8 files)
- src/app/**/*.tsx (15 files)
## Notes
- Use 'any' type as temporary escape hatch
- Focus on critical errors first, then warnings
- Update tests after each directoryUse the plan as context:
# Feed plan to agent
agent refactor "follow REFACTOR_PLAN.md" REFACTOR_PLAN.md tsconfig.json src/lib/Edge Case 1: Context Overflow
Large refactors exceed context windows, causing hallucinations:
# ❌ Bad: Too many files in context
agent refactor "add error handling" src/lib/database.ts src/lib/auth.ts src/lib/utils.ts src/lib/api.ts src/lib/cache.ts # ... 50 more filesStrategies to prevent overflow:
# 1. Use summaries instead of full files
agent summarize src/lib/ > LIB_SUMMARY.md
agent refactor "add error handling" LIB_SUMMARY.md
# 2. Work on subsets
agent refactor "add error handling to database" src/lib/database.ts
agent refactor "add error handling to auth" src/lib/auth.ts
# 3. Use diff context
git diff HEAD~10 > CHANGES.diff
agent review CHANGES.diff
# 4. Limit context explicitly
agent refactor --context-limit 10000 "add error handling" src/lib/Edge Case 2: The Verification Tax
Reviewing AI changes takes significant time:
// AI generated code - needs careful review
export async function getUser(id: string) {
// AI might:
// - Add unnecessary complexity
// - Use wrong patterns
// - Introduce subtle bugs
// - Break existing abstractions
const user = await db.users.findUnique({
where: { id },
include: {
posts: true, // Did we want this?
comments: true, // Or this?
profile: true, // Or this?
},
});
return user;
}Minimize verification tax with explicit constraints:
# Give clear, constrained instructions
agent refactor << 'EOF'
Add error handling to src/lib/database.ts.
Requirements:
1. Wrap all Prisma calls in try/catch
2. Log errors before rethrowing
3. Don't change existing function signatures
4. Don't add new dependencies
5. Keep the same logging format as other files in lib/
EOFPattern 3: Test-Driven Refactoring
Write tests before refactoring:
# Step 1: Write failing tests
agent test "write tests for database functions" src/lib/database.test.ts
# Step 2: Run tests to confirm failures
npm test src/lib/database.test.ts
# Step 3: Refactor to pass tests
agent refactor "make database tests pass" src/lib/database.ts src/lib/database.test.ts
# Step 4: Verify tests pass
npm test src/lib/database.test.tsTests as guardrails:
// src/lib/database.test.ts
describe('getUser', () => {
it('should return user with correct shape', async () => {
const user = await getUser('user-123');
expect(user).toMatchObject({
id: 'user-123',
name: expect.any(String),
email: expect.any(String),
});
});
it('should throw on not found', async () => {
await expect(getUser('invalid-id')).rejects.toThrow();
});
// Test ensures refactoring doesn't break behavior
});Edge Case 3: Inconsistent Patterns
AI agents apply different patterns across files:
// File 1: Uses async/await
export async function getData() {
const result = await fetch('/api/data');
return result.json();
}
// File 2: Uses Promise chain (inconsistent!)
export function getData() {
return fetch('/api/data')
.then(r => r.json());
}
// File 3: Uses different error handling
export async function getData() {
try {
const result = await fetch('/api/data');
return await result.json();
} catch (error) {
console.error(error);
throw error;
}
}Enforce consistency with patterns:
# Define pattern explicitly
agent refactor << 'EOF'
Add error handling to all files in src/lib/.
Pattern to follow:
```typescript
export async function functionName() {
try {
// existing code
return result;
} catch (error) {
logger.error('functionName failed', { error });
throw error;
}
}
```
Apply this exact pattern to all async functions.
EOFPattern 4: Verification Checklist
Systematic review of AI changes:
# VERIFICATION_CHECKLIST.md
## Before Committing AI Changes
- [ ] Code matches project patterns
- [ ] Type errors are fixed
- [ ] Tests pass
- [ ] No new dependencies added
- [ ] Error handling is consistent
- [ ] Logging follows existing format
- [ ] Comments are accurate
- [ ] No unnecessary complexity
- [ ] Performance not degraded
- [ ] Security best practices followedUse checklist with agent:
# Agent self-review
agent review --checklist VERIFICATION_CHECKLIST.md "review changes"
# Or manual review
git diff > CHANGES.patch
cat CHANGES.patch | agent verifyPattern 5: Rollback Strategy
Always keep rollback plan ready:
# Before refactoring
git branch -b refactor/add-error-handling
git commit -am "Before refactor: working state"
# Apply AI changes
agent refactor "add error handling" src/lib/
# Review and test
npm test
# If issues arise:
git reset --hard HEAD^ # Rollback
# Or use branch:
git checkout main # Switch back to working stateCommit granularly:
# Commit each file separately
git add src/lib/database.ts
git commit -m "Add error handling to database.ts"
git add src/lib/auth.ts
git commit -m "Add error handling to auth.ts"
# Makes rollback easier
git reset --hard HEAD~2 # Rollback last 2 commitsPattern 6: Human-AI Collaboration
Best approach: Humans design, AI implements:
# Human: Design the approach
# Create architecture document, define patterns, write tests
# AI: Implement specific tasks
agent implement "follow ARCHITECTURE.md and make tests pass" ARCHITECTURE.md tests/
# Human: Review and guide
# Review AI code, provide corrections, adjust architecture
# AI: Iterate based on feedback
agent fix "address review feedback" src/lib/database.tsKey Takeaways
- Incremental: Break large refactors into small steps
- Context management: Use summaries, diffs, explicit limits
- Verification tax: Systematic review with checklists
- Consistency: Define patterns explicitly, enforce them
- Test-driven: Write tests before refactoring
- Rollback: Keep working state, commit granularly
- Collaboration: Humans design, AI implements
Advertisement
Explore these curated resources to deepen your understanding
Official Documentation
Tools & Utilities
Related Insights
Explore related edge cases and patterns
Advertisement