Agent बड़े Refactor क्यों बिगाड़ते हैं (और उन्हें Scope कैसे करें ताकि न बिगड़ें)
मैंने Claude Code से कहा कि वह मेरे पूरे app में एक core domain type — Account को Workspace में rename करे, साथ में उससे जुड़े तीन fields भी। शायद साठ files थीं। इसने तेज़ी से शुरुआत की: पहले दर्जन files साफ थे, imports अपडेट हुए, call sites rename हुए, types thread हुए। मैंने इसे चलने दिया।
करीब तीस files के बाद, यह भटकने लगा। यह उन files में फिर से Account rename करने लगा जिन्हें यह बीस मिनट पहले ही touch कर चुका था, क्योंकि तब तक वे edits इसके context से बाहर हो चुके थे। दो files में इसने change आधा-अधूरा किया: type Workspace था, लेकिन एक method अभी भी पुरानी shape return कर रही थी। अंत में मैंने typecheck चलाया और नब्बे errors मिले, कुछ उस code में जो agent ने "finish" कर लिया था।
यह model की बेवकूफी नहीं थी। मैंने एक agent को ऐसा काम सौंपा जो उसके दिमाग में समा नहीं सकता था, बिना कोई रास्ता दिए कि वह रास्ते में खुद को जांच सके — एक junior dev को "जाओ यह सब जगह rename करो" कहकर एक घंटे के लिए छोड़ देने जैसा।
संक्षेप में: agent बड़े refactors पर एक structural कारण से fail होते हैं, intelligence की वजह से नहीं — change उनके भरोसेमंद ध्यान की context से बड़ा है, और कोई चीज़ उन्हें भटकने से पहले verify करने के लिए मजबूर नहीं करती। आप इसे scoping से ठीक करते हैं: पहले plan करो, काम को स्वतंत्र रूप से verifiable steps में काटो, हर step के बीच deterministic check चलाओ, और एक लिखित checklist रखो जिसे agent याद रखने के बजाय बार-बार पढ़े। जहाँ change mechanical है, agent को skip करो और codemod इस्तेमाल करो।
पहिए क्यों उतर जाते हैं
एक बड़े refactor में तीन चीज़ें compound होती हैं, और वे सब context तक वापस trace होती हैं।
Window भर जाती है और ध्यान सड़ जाता है। Anthropic का अपना best-practices doc इस बारे में सीधा कहता है: "Claude का context window तेज़ी से भर जाता है, और भरने के साथ-साथ performance घटती है... Claude पहले के instructions 'भूलना' शुरू कर सकता है या ज़्यादा गलतियाँ कर सकता है" (Claude Code best practices)। यह Claude के लिए unique नहीं है। "Lost in the middle" effect — जहाँ models एक लंबे context के बीच में बैठी information पर सबसे कम ध्यान देते हैं — अच्छी तरह documented है, और research दिखाती है कि degradation window पूरी भरने से पहले ही शुरू हो सकती है। जैसे-जैसे refactor चलता है, हर file read और हर command output उसी finite window में ढेर होती जाती है, आपके original instructions और आपके stated invariants को low-attention middle में धकेलती हुई।
Effective window sticker से छोटी है। Cursor users को यह लगातार मिलता है: 200K advertised window वाला model अक्सर केवल ~40–60K usable tokens रखता है जब harness का overhead, system prompt, और tool outputs account हो जाते हैं, और "तीसरे या चौथे step तक, agent पहले step में क्या किया उसकी degraded awareness के साथ काम कर रहा है" (Morph, Augment Code on Cursor multi-file refactors)। 50 files और 200K tokens के source वाला refactor simply fit नहीं होता, इसलिए files 31–50 उस agent द्वारा edit होते हैं जो files 1–30 भूल चुका है।
Cross-file contracts एक file-at-a-time editor को invisible हैं। Refactor का पूरा risk seams में रहता है: वह caller जिसने पुरानी return shape assume की, पुराने field name के आसपास बना fixture, वह serializer जो अभी भी पुरानी key लिखता है। Agent file A को सही edit करता है और file B को सही, लेकिन उनके बीच का contract एक ऐसी चीज़ है जो कोई single file नहीं बताती। वहीं half-applied changes और broken invariants रहते हैं।
मेरे जैसे clean rename के लिए, पहला ईमानदार सवाल है कि क्या कोई agent को इसे touch करना चाहिए।
जब change mechanical हो तो codemod इस्तेमाल करें
अगर transformation structural और rule-shaped है — symbol rename करना, call signature बदलना, import swap करना — तो एक deterministic AST codemod हर ज़रूरी dimension पर agent को पीट देता है। jscodeshift जैसे tools abstract syntax tree पर काम करते हैं, इसलिए वे हर matching node पर exact same transform apply करते हैं, parallel में, पूरे codebase में "seconds में," बिना दो files के अलग हो जाने की कोई संभावना के (Toptal, Martin Fowler on codemods)। एक agent, इसके विपरीत, एक बड़े codebase पर "hallucination की संभावना भी रखेगा, हर बार थोड़े अलग नतीजे देगा, और यह slow और costly है" (Carlos Cuesta)।
जो move वास्तव में agent की strength use करती है: उसे codemod लिखवाओ, edit perform नहीं। आप transform को English में describe करते हो, वह jscodeshift transform generate करता है, आप इसे तीन files पर test करते हो, फिर सभी साठ पर deterministically चलाते हो। आपको rule express करने में agent की fluency और AST tool की guarantee मिलती है कि rule हर जगह identically apply होती है।
# Agent transforms/account-to-workspace.js लिखता है, आप चलाते हो:
npx jscodeshift -t transforms/account-to-workspace.js src/ --dry # preview
npx jscodeshift -t transforms/account-to-workspace.js src/ # apply
git diff --stat # 60 files, one deterministic transform
मेरा rename 80% mechanical था। सही call थी renames के लिए codemod और केवल ~12 files के लिए agent जिनमें judgment चाहिए था — serializer, migration, वे जगहें जहाँ transition के दौरान Account और Workspace को coexist करना था। Agent के लिए तब reach करो जब change को intent पढ़ने की ज़रूरत हो, जब rule apply करने की ज़रूरत हो तब नहीं।
पहले plan करो, एक ऐसे mode में जो edit नहीं कर सकता
जब change को genuinely एक agent की ज़रूरत हो, इसे turn one पर editing शुरू मत करने दो। सबसे बड़ा lever है exploration और planning को execution से अलग करना, ताकि agent disk touch करने से पहले एक approach पर commit करे।
Claude Code में यह plan mode है (Shift+Tab, या --permission-mode plan): agent files पढ़ता है, imports trace करता है, और zero edits करते हुए एक plan produce करता है, और आप पहले इसे approve करते हो (common workflows)। Aider का equivalent split है — restructuring plan करने के लिए /architect, फिर execute करने के लिए /code (refactoring a large codebase with aider)। बात ceremony की नहीं है। Agent को "directly coding पर jump करने देने से ऐसा code produce हो सकता है जो गलत problem solve करे," और आप जो plan दो मिनट में पढ़ते हो वह इसे एक घंटे के edits की लागत आने से पहले पकड़ लेता है (best practices)।
Doc इस बारे में एक साफ line खींचता है कि कब इसकी परवाह करें: "अगर आप diff को एक sentence में describe कर सकते हैं, plan skip करें।" एक multi-file refactor कभी एक sentence में fit नहीं होता। इसे plan करो।
बड़े jobs के लिए एक useful detail: पहला plan accept मत करो जो "सभी 60 files edit करो" में decompose हो। उसे dependency order में refactor करने के लिए push करो — उन modules से शुरू करो जिन पर और कुछ depend नहीं करता और बाहर की तरफ काम करो — और काम को verify करने के लिए काफी छोटे phases में split करो। Practice में, agents एक pass में roughly 20–40 files में reliable रहते हैं; उससे आगे, consensus है phases में split करो और उन्हें separate sessions के रूप में run करो हर एक के बीच एक commit के साथ (Claude Code multi-file refactoring walkthrough)।
इसे independently-verifiable steps में काटो हर एक के बीच एक gate के साथ
यही वह हिस्सा है जो actually आपको बचाता है। एक refactor जो agent एक undifferentiated sweep में करता है उसमें कोई internal checkpoints नहीं हैं, इसलिए पहली error चुपचाप उसके बाद सब कुछ corrupt करती है। Steps में काटा गया refactor — हर एक step एक check के साथ खत्म होती है जो pass या fail होती है — एक mistake को उस gate से आगे propagate नहीं होने दे सकती जो उसे पकड़ती है।
Anthropic पूरी discipline को इसके आसपास frame करता है: "Claude काम done दिखने पर रुक जाता है। उसके बिना एक check जो वह run कर सके, 'done दिखता है' एकमात्र available signal है, और आप verification loop बन जाते हो।" उसे एक check दो जो pass/fail return करे — test suite, build exit code, typecheck — "और loop अपने आप close हो जाता है" (best practices)। Check "agent सोचता है यह done है" को "typechecker agree करता है यह done है" में convert करती है।
यहाँ वह staged plan है जो मुझे Account → Workspace rename के लिए इस्तेमाल करना चाहिए था, dependency order में, हर step के बाद gate के साथ:
PLAN: Account -> Workspace rename. हर step के बाद जो gate pass करे commit करो।
[ ] 1. src/domain/ में type + fields rename करो (कोई dependents नहीं)।
GATE: src/domain/ में tsc --noEmit clean। Commit।
[ ] 2. Data layer (repositories, serializers) जो type import करते हैं update करो।
GATE: tsc --noEmit clean; src/data/ के लिए unit tests pass। Commit।
[ ] 3. Service/API layer call sites update करो।
GATE: पूरे repo में tsc --noEmit clean; integration tests pass। Commit।
[ ] 4. Test fixtures + factories को नए field names में update करो।
GATE: full test suite green। Commit।
[ ] 5. UI components + कोई भी बचे हुए string references update करो।
GATE: tsc --noEmit clean; build succeeds; e2e smoke pass। Commit।
हर step context में fit होने के लिए काफी छोटी है, एक deterministic gate पर खत्म होती है, और अगली शुरू होने से पहले commit होती है। अगर step 3 नब्बे errors फेंकती है, आप इसे step 3 पर देखते हो — steps 1 और 2 git में already safe के साथ — न कि अंत में सभी साठ files में। Commit gate जितनी ज़रूरी है: एक botched step एक git reset दूर है, archaeology project नहीं।
Agent को gate खुद run करने और output दिखाने के लिए कहो, success assert करने के लिए नहीं। "Claude को success assert करने के बजाय evidence दिखाने के लिए कहो: test output, वह command जो उसने run किया और क्या return हुआ" (best practices)। इसे hard-enforce करने के लिए, एक Stop hook check को script के रूप में run कर सकता है और turn को तब तक खत्म होने से मना कर सकता है जब तक यह pass नहीं हो जाता।
Checklist एक ऐसी file में रखो जो agent खो नहीं सकता
ऊपर का plan तभी मदद करता है जब agent के पास step 5 में यह हो। अगर यह केवल conversation में रहता है तो नहीं रहेगा — step 5 तक early steps window से age out हो चुके हैं, जो exactly वही failure है जिससे हमने शुरुआत की थी। इसलिए checklist एक file में जाती है: repo में PLAN.md या REFACTOR.md, agent को इसे पढ़ने, अगला unchecked step करने, gate run करने, और box tick करने के लिए instruct किया जाता है।
Anthropic exactly यही recommend करता है long, exhaustive jobs के लिए: "code migrations, numerous lint errors ठीक करने, या complex build scripts run करने" के लिए, Claude को "Markdown file (या GitHub issue!) को checklist और working scratchpad के रूप में इस्तेमाल करने दो" (best practices)। Repo में एक file external memory है। Agent इसे हर turn पर re-read करता है, इसलिए plan bloated context के middle में decay होने के बजाय full attention पर बनी रहती है। Ticked boxes वास्तव में क्या done है उसका durable record हैं — वह नहीं जो agent अस्पष्ट रूप से याद करता है।
इसे context hygiene के साथ pair करो। Steps के बीच — या separate sessions के रूप में run होने वाले phases के बीच — /clear ताकि अगला step clean शुरू हो, केवल checklist और current step की files load हों। किसी भी "जाओ X कैसे काम करता है पढ़ो" exploration को एक subagent को delegate करो ताकि reading एक separate window में हो और केवल summary वापस आए। (इन दोनों के अपने posts हैं यहाँ — मैं उन्हें फिर से litigate नहीं करूँगा।)
एक verification pass जिसने code नहीं लिखा
एक आखिरी gate, steps done होने के बाद: पूरे diff को एक fresh agent से review करवाओ जिसने implementation कभी नहीं देखी। एक reviewer "fresh subagent context में run होता है जो केवल diff और आपके दिए criteria देखता है, वह reasoning नहीं जिसने change produce किया, इसलिए यह result को अपने terms पर evaluate करता है" (best practices)। इसे plan पर point करो:
REFACTOR.md के खिलाफ full diff review करने के लिए subagent use करो। Check करो कि
हर step का change fully apply है — कोई file पुरानी type या पुराने field names के साथ
नहीं बची, कोई method अभी भी पुरानी shape return नहीं कर रही। केवल वे gaps report करो
जो correctness या stated requirement को break करते हैं, style नहीं।
वह आखिरी sentence matter करती है। Gaps खोजने के लिए कहा गया reviewer उन्हें manufacture करेगा — "extra abstraction layers, defensive code, और ऐसे cases के लिए tests जो हो नहीं सकते" — इसलिए इसे correctness और contract तक scope करो, नहीं तो आप एक clean refactor को एक हफ्ते के gold-plating से trade कर लोगे (best practices)।
जो बात मैं बार-बार दोबारा सीखता हूँ: agent एक बड़े refactor में bottleneck नहीं है — scope है। 60-file rename पूरा सौंपा जाए तो drift होगा, half-apply होगा, और done होने का झूठ बोलेगा, चाहे model कितना भी अच्छा हो। वही rename पाँच dependency-ordered steps में काटी जाए, हर एक typecheck से gated, हर एक committed, एक file में tracked जिसे agent re-reads — वह finish होता है, क्योंकि किसी point पर मैंने उससे उतना hold करने को नहीं कहा जितना वह कर सकता था। इसे plan करो, इसे slice करो, इसे gate करो, लिख दो। और जब change केवल एक rule हो, codemod लिखो और AST को वह हिस्सा करने दो जिसमें agents सबसे कमज़ोर हैं।
