Recursively upgrades all source files in a directory to enterprise quality using Claude Code/OpenCode CLI, maintaining a single conversation so the AI builds context as it goes. Auto-rotates to a new session when context fills up. Commits to a feature branch by default so you review before merging.
| Feature | v2 | v3 |
|---|---|---|
| Default backend | API (separate billing) | Claude Code CLI (free with Pro/Max) or OpenCode (free, no auth, any model) |
| Git strategy | Commits directly to current branch | Creates upgrade branch → you review → merge |
| Session management | -c (continue most recent — fragile) |
--session-id <uuid> (deterministic) |
| Reasoning depth | None | --effort max + adaptive thinking (API) |
| Permission handling | --permission-mode (can be overridden) |
--dangerously-skip-permissions (hard bypass) |
| Structured output | Regex parsing of markdown | --json-schema structured JSON |
| Syntax validation | None — broken code gets committed | ast.parse() for Python, node --check for JS |
| Retry on failure | Skip and move on | Re-prompts with error context (up to 2 retries) |
| State tracking | Injects # ENTERPRISE-UPGRADED into source |
External .upgrade-state.json — no code pollution |
| Output sanity check | None | Rejects responses <40% or >500% of original size |
| Context management | while messages > 42 (arbitrary trim) |
Token-based: auto-rotates session at 90% full |
| Context window | 200K assumed for all models | 1M for opus/sonnet, 200K for everything else |
| Token tracking | None (CLI) | Per-call context %, cumulative cost, session count |
| Batch fallback | Parse failure = file skipped | Falls back to individual upgrade |
| Cold start | Full init every call | --bare skips hooks/LSP/plugins (~2s faster) |
| Overload resilience | Fails | --fallback-model sonnet auto-fallback |
| Hybrid mode | N/A | Two-pass: Opus plans → Sonnet executes |
| File exploration | N/A | Optional --explore with hard read limits |
# Upgrade entire backend (CLI is default — free with Pro/Max, no API key needed)
.\upgrade.ps1 -ProjectPath "C:\Users\subha\projects\penora\backend"
# Specific patterns, commit every 5 files
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -CommitEvery 5
# Push the upgrade branch to origin (create PR from GitHub UI)
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -Push
# Skip branch creation — commit directly to current branch
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -Direct
# Custom branch name
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -Branch "upgrade/my-feature"
# Stateless mode — no session clutter (trades cross-file context for clean UI)
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -NoPersist
# Frontend files
.\upgrade.ps1 -ProjectPath "." -Include "src/**/*.ts","src/**/*.tsx"
# Dry-run (preview in /upgraded/ folder, nothing overwritten)
.\upgrade.ps1 -ProjectPath "." -Include "backend/**/*.py" -DryRun
# Resume after interruption (reads .upgrade-state.json)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -SkipDone
# Start fresh (clear state from previous run)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -ResetState
# Use API backend instead (separate billing, requires ANTHROPIC_API_KEY)
.\upgrade.ps1 -ProjectPath "." -Backend api -ApiKey "sk-ant-..."
# Use OpenCode CLI backend (free, no API key, any model/provider)
.\upgrade.ps1 -ProjectPath "." -Backend opencode -Model "anthropic/claude-sonnet-4-20250514"
# Start from an existing branch instead of HEAD (for incremental upgrades)
.\upgrade.ps1 -ProjectPath "." -FromBranch "upgrade/enterprise-20260404-1958"
# Budget cap for large runs
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -MaxBudget 10.00Two-pass upgrade: a planning model analyzes the file, then an execution model writes the code. Use different models for each pass to balance quality and speed.
# Opus plans, Sonnet executes (recommended)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -Hybrid -Model sonnet -PlanningModel opus
# Same model for both passes (just effort levels differ)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -HybridHow it works:
- Pass 1 (Planning): The planning model (default: same as
--model, or--planning-modeloverride) analyzes the file with high effort and produces a numbered improvement plan. No code is written. - Pass 2 (Execution): The execution model (
--model) receives the plan and the file, then writes the complete upgraded code.
Effort levels are capped automatically: max is Opus-only, Sonnet caps at high.
By default, the AI works only with the file content provided — no tools, no file reads. This keeps context lean and upgrades fast.
With --explore, the AI gets Read access to the project so it can check imports, shared types, and interfaces before upgrading:
# Explore with defaults (max 5 reads per file, cutoff at 500k context)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -Explore
# Allow up to 10 reads per file
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -Explore -MaxReads 10
# Very conservative: 2 reads per file
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -Explore -MaxReads 2
# Hybrid + explore (only the planning pass gets Read access)
.\upgrade.ps1 -ProjectPath "." -Include "**/*.py" -Hybrid -PlanningModel opus -Model sonnet -Explore -MaxReads 5Two hard limits prevent runaway context growth:
| Limit | Mechanism | Effect |
|---|---|---|
--max-reads N (default 5) |
--max-turns N+1 passed to CLI |
After N file reads, the CLI forcibly stops the agentic loop |
| Context > 500k tokens | Read tool revoked (--tools "") |
Once session accumulates 500k tokens, explore is disabled for all subsequent calls |
Both limits reset per file. In hybrid mode, only the planning pass gets Read access — the execution pass never explores.
When --explore is off, the agent gets --tools "" (no tools at all). This is a hard CLI-level enforcement, not a prompt instruction.
┌──────────────────────────────────────────────────────────────────┐
│ │
│ git checkout -b upgrade/enterprise-20250324-1430 │
│ │
│ Session #1 (opus/sonnet = 1M token context) │
│ │
│ 1. Send project overview + file manifest │
│ 2. Send auth.py → get upgraded auth.py │
│ → validate syntax (ast.parse) ✓ │
│ → sanity check size ✓ │
│ → write file + commit to upgrade branch │
│ → log: "Context: 45,000 / 1,000,000 tokens (5%)" │
│ │
│ 3. Send users.py → upgraded (uses auth.py context) │
│ → syntax error! → retry with error message │
│ → 2nd attempt passes ✓ → write + commit │
│ │
│ ... 40 more files ... │
│ │
│ 44. Context: 910,000 / 1,000,000 (91%) │
│ ⟳ SESSION ROTATED → #2 (fresh context + system prompt) │
│ │
│ Session #2 │
│ 45. Send routes.py → upgraded (patterns from system prompt) │
│ → log: "Context: 35,000 / 1,000,000 tokens (4%)" │
│ │
│ ... continues until all files done ... │
│ │
│ git push -u origin upgrade/enterprise-20250324-1430 (if --push)│
│ git checkout main (switch back) │
│ │
│ → You review the branch, create a PR, merge when satisfied │
│ │
└──────────────────────────────────────────────────────────────────┘
| Flag | Default | Description |
|---|---|---|
| Files | ||
--include |
**/*.* |
Glob patterns for files |
--exclude |
smart defaults | Extra patterns to skip |
| Backend | ||
--backend |
cli |
cli (Claude Code CLI, free), api (Anthropic API, separate billing), or opencode (OpenCode CLI — free, no auth, any model/provider) |
--model |
opus (CLI) / claude-opus-4-6 (API) / opencode/qwen3.6-plus-free (OpenCode) |
Model for code execution |
--planning-model |
same as --model |
Model for planning pass in --hybrid mode (e.g. opus) |
--effort |
max |
Reasoning depth: low, medium, high, max (max is Opus-only, Sonnet caps at high) |
--fallback-model |
sonnet |
Auto-fallback when primary model is overloaded |
--max-budget |
unlimited | Max USD to spend (CLI backend) |
--no-persist |
off | Stateless: no session saved (clean UI, no cross-file context) |
--no-json-schema |
off | Disable structured output (use markdown regex parsing) |
| Hybrid Mode | ||
--hybrid |
off | Two-pass: planning pass (high effort) → execution pass. Doubles CLI calls per file. |
--planning-model |
same as --model |
Model for the planning pass (e.g. opus while --model sonnet) |
| Exploration | ||
--explore |
off | Allow AI to Read related project files for context before upgrading |
--max-reads |
5 |
Max files the AI can Read per file upgrade (enforced via --max-turns at CLI level) |
--no-explore-cutoff |
off | Disable the 500k token auto-revoke of Read tool — explore keeps working regardless of context size |
| Git | ||
--branch |
auto | Create upgrade branch (auto-named upgrade/enterprise-YYYYMMDD-HHMM). Pass a name to override. |
--direct |
off | Skip branch creation, commit to current branch. Not recommended for production. |
--from-branch |
off | Create upgrade branch from an existing branch (e.g. --from-branch upgrade/enterprise-20260404-1958). Enables linear upgrade chains across runs. |
--push |
off | Push upgrade branch to origin after completion. |
--commit-every |
1 |
Git commit after every N files |
--no-commit |
off | Disable git commits |
| Processing | ||
--no-batch |
off | Process every file individually |
--batch-size |
3 |
Small files per batch prompt |
--dry-run |
off | Save to /upgraded/ instead of overwriting |
--delay |
2.0 |
Seconds between calls |
| State | ||
--skip-done |
off | Skip files in .upgrade-state.json |
--reset-state |
off | Clear state file and start fresh |
--system-prompt |
system_prompt.md |
Custom system prompt file |
By default, the upgrader creates a feature branch before making any changes:
main ──●──────────────────────────────●── (untouched)
\ /
upgrade/enterprise-... ──●──●──●──●── (all upgrades here)
| Mode | Command | Behavior |
|---|---|---|
| Branch (default) | .\upgrade.ps1 -ProjectPath . |
Creates branch, commits there, switches back to original when done |
| Branch + push | .\upgrade.ps1 -ProjectPath . -Push |
Same + pushes branch to origin (create PR from GitHub UI) |
| Custom branch | .\upgrade.ps1 -ProjectPath . -Branch "upgrade/v2" |
Your branch name instead of auto-generated |
| Direct | .\upgrade.ps1 -ProjectPath . -Direct |
Old behavior: commits to current branch (no branch creation) |
After completion, the summary shows your next steps:
============================================================
DONE — 47/50 upgraded, 3 failed
Tokens: 2,450,000 | Context: 34% of 1000K | Sessions: 3
Branch: upgrade/enterprise-20250324-1430 (local only)
Push: git push -u origin upgrade/enterprise-20250324-1430
Merge: git checkout main && git merge upgrade/enterprise-20250324-1430
Or PR: create pull request from upgrade/enterprise-20250324-1430 → main
============================================================
Every upgraded file passes through 3 checks before being written:
- Parse check — Can the code be extracted from the LLM response? (JSON schema or regex on markdown code blocks)
- Sanity check — Is the output between 40%–500% of the original size? (catches truncation and hallucination)
- Syntax check — Does it parse? Python:
ast.parse(). JavaScript:node --check. Others: skipped.
If any check fails, the file is re-prompted with the specific error. Up to 2 retries. If all fail, the file is marked as failed in .upgrade-state.json and the raw response is saved to .upgrade-debug/ for manual review.
The source of truth is total context tokens — input_tokens + cache_creation_input_tokens + cache_read_input_tokens from the API response. This is the actual context the model processes per call, including cached portions.
| Model | Context Window | ~Files per session |
|---|---|---|
opus / sonnet |
1,000,000 tokens | ~80-120 files |
| Everything else | 200,000 tokens | ~15-25 files |
Auto-rotation at 90%: When context tokens >= 90% of limit, the upgrader generates a new session ID. The next call starts a fresh conversation with the system prompt re-sent. Cross-file context is lost, but the system prompt carries all the architectural patterns.
Session recovery: If a CLI call times out, the session ID may be tainted. The upgrader auto-recovers by either regenerating a fresh UUID or switching to --resume mode for the existing session.
Log output during a run:
14:30:35 INFO Context: 45,231 / 1,000,000 tokens (5%)
...
15:12:44 WARNING Context 78% full — approaching rotation threshold
...
15:45:02 WARNING ⟳ SESSION ROTATED → #2 (was 91% full, old=a3f2b1c8…, new=7e4d9f12…)
Final summary:
Tokens: 2,450,000 (in: 2,100,000, out: 350,000) | Cost: $4.21 | Context: 34% of 1000K | Sessions: 3
State is tracked in .upgrade-state.json at the project root:
{
"completed": {
"backend/auth.py": {
"timestamp": "2025-03-24T14:30:00",
"changes": "- Added typed exception hierarchy\n- Extracted config dataclass",
"original_lines": 120,
"upgraded_lines": 340
}
},
"failed": {
"backend/legacy.py": "Syntax error after 2 retries: unexpected indent at line 45"
},
"started_at": "2025-03-24T14:00:00",
"last_updated": "2025-03-24T14:30:00"
}your-project/
├── .upgrade-state.json # Progress tracking (resume-safe)
├── .upgrade-logs/ # Change summaries (one per file)
│ ├── backend__auth_changes.md
│ └── ...
├── .upgrade-debug/ # Raw LLM responses (only on failures)
│ └── legacy_raw.txt
└── (your upgraded source files)
- Start with
--dry-runto review before overwriting - Use
--skip-doneto resume interrupted runs — reads.upgrade-state.json - Commit your work first before running without
--dry-run - Use
--pushto push the upgrade branch and create a PR from the GitHub UI - Use
--directonly when you've already created a branch manually - Use
--commit-every 5for cleaner git history - Edit
system_prompt.mdto add project-specific patterns - Check
.upgrade-debug/when files fail — the raw LLM response is there - Use
--reset-stateto re-process files that were previously completed - Use
--no-persistif you don't want upgrader sessions cluttering your Claude Code session list - For huge codebases (100+ files), the auto-rotation handles context overflow automatically
- Use
--max-budget 5.00to cap spend on large runs - Use
--hybrid -PlanningModel opus -Model sonnetfor best quality/speed balance - Use
--explore -MaxReads 3when the AI needs to understand shared types and interfaces - Use
--backend opencodefor a free backend with no API key — any model viaprovider/modelformat (e.g.anthropic/claude-sonnet-4-20250514,google/gemini-2.5-pro,openai/gpt-5.4) - Use
--from-branchto chain upgrades incrementally — start from the branch of a previous run and pick up where you left off - OpenCode uses regex parsing (no
--json-schemasupport) and auto-approves all permissions by default