Problem: AI Runs Out of Memory Mid-Refactor
You're refactoring a 50-file codebase with Claude or another AI assistant. Halfway through, you hit "Context window exceeded" and lose all progress. Your partial changes are scattered across files, and you can't remember what was supposed to happen next.
You'll learn:
- Why context limits break large refactors
- How to chunk work without losing state
- Tool patterns that preserve context
- When to stop using AI chat interfaces
Time: 20 min | Level: Intermediate
Why This Happens
LLMs have fixed context windows (typically 200k-1M tokens). When you paste entire files, conversation history, and error messages, you consume this budget fast. A single TypeScript file can be 2k-5k tokens—paste 20 files and you're already at 40k-100k tokens before adding any discussion.
Common symptoms:
- "Context window exceeded" mid-conversation
- AI forgets earlier decisions or file contents
- Repeated questions about code you already shared
- Incomplete refactors with no clear continuation point
Why chat interfaces fail:
- Every message includes full conversation history
- File contents get re-sent on every turn
- No persistent state between sessions
- Linear conversation model doesn't match branching refactor work
Solution
Step 1: Use Architecture-Aware Chunking
Break refactors by architectural boundaries, not arbitrary file counts.
Good chunk boundaries:
- Single module with clear inputs/outputs
- One abstraction layer (data → service → API)
- Feature-complete user story
- Files that import each other (dependency clusters)
Bad chunk boundaries:
- Alphabetical file lists
- Random 10-file batches
- Half of a module
Example chunk plan for migrating auth:
Chunk 1: Data models + types (no dependencies)
- user.types.ts, auth.types.ts, session.types.ts
Result: Type definitions complete, can be used anywhere
Chunk 2: Core auth service (depends on Chunk 1)
- authService.ts, sessionManager.ts, tokenValidator.ts
Result: Business logic migrated, ready for API layer
Chunk 3: API routes (depends on Chunk 1 + 2)
- authRoutes.ts, middleware.ts
Result: End-to-end auth flow working
Why this works: Each chunk produces working, testable code. If you hit context limits, previous chunks are done—not half-done.
Step 2: Create Persistent State Documents
Don't rely on conversation memory. Create markdown documents the AI can reference.
Create REFACTOR_STATE.md in your repo:
# Auth Migration State
## Completed
- ✅ Migrated user types to Zod schemas (Chunk 1)
- ✅ Updated authService to use new types (Chunk 2)
## Current Chunk: API Routes (Chunk 3)
**Goal:** Migrate /api/auth/* routes to new service
**Files in scope:**
- src/routes/authRoutes.ts (350 lines)
- src/middleware/authMiddleware.ts (120 lines)
**Changes needed:**
1. Replace old User type with new schema
2. Update error handling to match service
3. Add rate limiting middleware
**Blockers:** None
## Next Chunks
- Chunk 4: Frontend auth components
- Chunk 5: Tests + cleanup
## Decisions Log
- Using Zod for runtime validation (not io-ts)
- Sessions stay in Redis, not moving to DB
- Breaking change: JWT payload structure different
Usage pattern:
- Start each session: "Read REFACTOR_STATE.md, continue Chunk 3"
- After each chunk: Update the document
- If you get context errors: State persists, restart with smaller scope
Why this works: State lives in your repo, not AI memory. You can switch tools, pause work, or collaborate without losing context.
Step 3: Use File-Based Tools Instead of Chat
Chat interfaces aren't designed for large refactors. Use tools with persistent file access.
Tools that manage context better:
| Tool | How it helps | Best for |
|---|---|---|
| Cursor / Windsurf | Index entire codebase, fetch files on demand | Multi-file refactors with complex dependencies |
| Aider | Git-aware, only loads files you specify | Surgical changes in large repos |
| Claude Code | Terminal-based, stateful conversations | Backend refactors, script-driven changes |
| GitHub Copilot Workspace | Maintains task state across sessions | Long-running migrations |
Pattern: Targeted file loading
# Aider example - only load files you're changing
aider src/auth/authService.ts src/types/user.types.ts
# Reference related files without loading them
/ask "Should authService.ts import from ../utils/crypto.ts?"
# Aider reads crypto.ts on demand, doesn't keep in context
Pattern: Incremental commits
# After each chunk, commit immediately
git add src/auth/
git commit -m "Chunk 2: Migrate auth service to new types"
# If context explodes, you have working code saved
# Start fresh conversation with: "Continue from last commit"
Why this works: Tools index your codebase separately. They load files on demand rather than keeping everything in context. State persists through git commits.
Step 4: Design Continuation Points
Plan how to resume if context runs out.
Bad continuation point:
"Finish updating all the API routes"
If context runs out here, you don't know which routes are done.
Good continuation points:
## Resume Instructions
If interrupted:
1. Check which files have `// MIGRATED: Chunk 3` comment
2. Continue with next file in list below
3. Test pattern: `npm test -- authRoutes`
**File checklist:**
- [x] authRoutes.ts - login/logout done
- [x] authRoutes.ts - token refresh done
- [ ] authRoutes.ts - password reset (NEXT)
- [ ] middleware.ts - auth check
- [ ] middleware.ts - rate limiter
Use marker comments in code:
// MIGRATED: Chunk 3 - New auth service integration
// NEXT: Update error handling to match service pattern
export function loginHandler(req: Request, res: Response) {
// ... new code ...
}
If context fails:
- Open REFACTOR_STATE.md
- See last completed marker
- Continue from next checklist item
Why this works: Explicit checkpoints make refactors resumable. You never lose more than one sub-task of work.
Step 5: Measure Your Context Usage
Know when you're approaching limits before you hit them.
Token estimation:
- 1 token ≈ 4 characters (rough average)
- TypeScript file: ~1 token per 3-4 characters
- Conversation turn: 100-500 tokens overhead
Quick check in Claude:
"How many tokens is this conversation using?"
When to split your request:
| Usage | Action |
|---|---|
| < 50k tokens | Continue normally |
| 50k-150k tokens | Consider splitting next chunk |
| 150k+ tokens | Start fresh conversation, commit work |
| > 180k tokens | You're about to hit limit (200k window) |
Pattern: Pre-emptive split
**Current context estimate:** ~140k tokens
Instead of: "Now migrate all 8 remaining routes"
Do this: "Migrate routes/auth.ts only. I'll start new chat for routes/users.ts"
Why this works: Proactive splitting prevents mid-task failures. Each chunk completes cleanly.
Verification
Test your refactor is resumable:
# Simulate context failure mid-chunk
git stash # Save partial work
# Can you continue from REFACTOR_STATE.md?
cat REFACTOR_STATE.md
# Should clearly say: "Current file: X, Next step: Y"
git stash pop
You should see:
- Clear marker of last completed file
- Explicit next action
- No ambiguity about partial work
What You Learned
- Context windows are finite—chat history compounds fast
- Chunk by architecture, not arbitrary limits
- Persistent state documents beat conversation memory
- File-based tools > chat interfaces for large refactors
- Continuation points prevent losing work
Limitations:
- This doesn't help with single-file refactors >100k tokens (those need algorithmic approaches)
- State documents require discipline to maintain
- Tool switching has learning curve
Advanced Patterns
Pattern 1: Hierarchical Context Documents
For very large refactors (100+ files), use nested state documents:
docs/refactor/
MASTER_PLAN.md # Overall strategy
auth-migration/
STATE.md # Auth-specific state
DECISIONS.md # Why we chose X over Y
api-redesign/
STATE.md
BREAKING_CHANGES.md
Reference hierarchy in prompts:
"Read auth-migration/STATE.md and auth-migration/DECISIONS.md.
Continue with current chunk."
Pattern 2: Diff-Only Updates
Instead of pasting entire files, use diffs:
# Generate compact diff for AI
git diff --no-color main src/auth/ > auth.diff
# In prompt: "Apply this diff to continue refactor: [paste auth.diff]"
Token savings: Diffs are 80-90% smaller than full files. A 500-line file might be a 50-line diff.
Pattern 3: Symbol-Level Loading
For tools that support it (Cursor, Windsurf), reference by function name:
"Update the validateToken function in authService.ts to use the new TokenPayload type"
Tool loads only that function's context, not the entire 2000-line file.
Common Pitfalls
Pitfall 1: Kitchen sink prompts
❌ "Here's my whole codebase [paste 50 files]. Refactor it."
✅ "Here's authService.ts [paste 1 file]. Migrate to new types following REFACTOR_STATE.md pattern."
Pitfall 2: No commit discipline
❌ Working for 2 hours across 20 files, hit context limit, nothing committed
✅ Commit after each chunk (every 20-30 min). If context fails, you have working code.
Pitfall 3: Resuming from memory
❌ "Continue where we left off" [AI hallucinates previous state]
✅ "Read REFACTOR_STATE.md. Continue from the Chunk 3 checklist."
Pitfall 4: Ignoring token budgets
❌ Keep pasting files until you hit the wall
✅ Check token usage at 50k, 100k, 150k. Split proactively.
Real-World Example
Scenario: Migrating 80-file Express.js API to Fastify.
Failed approach (context explosion):
- Paste 10 route files (30k tokens)
- Ask to migrate all to Fastify
- AI starts migration, conversation hits 150k tokens
- Ask to continue with middleware files
- Context exceeded at file 6/10
- Half-migrated codebase, unclear what's done
Working approach (chunked with state):
# FASTIFY_MIGRATION.md
## Chunk 1: Core setup (DONE)
- ✅ fastify app.ts initialization
- ✅ plugin architecture setup
- ✅ error handling middleware
## Chunk 2: Auth routes (CURRENT)
Goal: Migrate 5 auth route files
Files:
- [x] routes/auth/login.ts
- [x] routes/auth/logout.ts
- [ ] routes/auth/refresh.ts (IN PROGRESS)
- Line 45: Need to update token validation
- Next: Update response format
- [ ] routes/auth/register.ts
- [ ] routes/auth/reset.ts
Resume: "Continue routes/auth/refresh.ts from line 45"
## Chunk 3: User routes (NEXT)
...
Result:
- Each chunk took 20-30 min, stayed under 50k tokens
- Context limits hit twice, but REFACTOR_STATE.md made resuming trivial
- Full migration completed over 3 days (8 chunks)
- Every chunk produced working, tested code
Tool-Specific Tips
Cursor / Windsurf
- Use @workspace to reference files without loading them
- Use Cmd+K for inline edits (loads minimal context)
- Enable "Auto-commit" to save progress automatically
Aider
# Add files incrementally as you need them
/add src/auth/service.ts
# Drop files when done to free context
/drop src/auth/service.ts
# Check context usage
/tokens
Claude Code (via CLI)
# Stateful - keeps conversation across invocations
claude-code "Start REFACTOR_STATE.md Chunk 3"
# Later, even after closing terminal:
claude-code "Continue last refactor"
GitHub Copilot Workspace
- Use "Tasks" feature to break work into chunks
- Each task maintains its own context
- Failed tasks can be retried without affecting others
When to Stop Using AI
Some refactors are too large for AI assistance, even with chunking:
Use AI when:
- < 100 files affected
- Clear architectural boundaries
- Repetitive transformations
- Well-defined target state
Don't use AI when:
200 files with complex interdependencies
- Unclear target architecture (figure it out first)
- Safety-critical code requiring formal verification
- Changes require deep domain expertise AI doesn't have
Instead:
- Use AI to generate codemods/AST transforms, then run them
- Have AI write test coverage first, then refactor manually
- Break work into <100 file chunks over weeks/months
Tested with Claude 3.7 Sonnet, Cursor 0.43+, Aider 0.62+, on TypeScript 5.5+ codebases