AgentがなぜBig Refactorを台無しにするのか(そしてスコープをどう設定すれば防げるか)
私はClaude Codeに、アプリ全体のコアドメイン型を Account から Workspace に改名するよう頼んだ。それに付随する3つのフィールドも含めて。おそらく60ファイル。最初は速かった:最初の数十ファイルはきれいに処理され、importsが更新され、call sitesが改名され、型が伝播した。そのまま走らせた。
30ファイルほどで、漂い始めた。20分前にすでに触れていたファイルで再び Account を改名し始めたのだ。そのときにはその編集がコンテキストから外れていたからだ。2つのファイルでは変更が半分しか適用されていなかった:型は Workspace だったが、メソッドはまだ古い形式を返していた。最後に実行したtypecheckは90個のエラーを出した。Agentが「完了した」コードにも含まれていた。
これはモデルが馬鹿だったからではない。私が一人のAgentに、頭の中で保持できる以上の仕事を渡し、途中で自己確認する手段を与えなかったのだ。「全部のところで改名してきて」とジュニア開発者に言って1時間席を外すのと、Agentで言えば同じことだ。
要約: Agentが大きなリファクタリングで失敗するのは知能の問題ではなく、構造的な理由からだ。変更が、信頼できる形で注意を向けられるコンテキストよりも大きく、漂流する前に検証を強制するものが何もない。スコープで修正する:まず計画し、作業を独立して検証できるステップに切り分け、各ステップの間に決定論的なチェックを実行し、記憶ではなく再読する書面のチェックリストを維持する。変更が機械的な場合は、Agentをスキップしてcodemoduを使用する。
なぜ車輪が外れるのか
大きなリファクタリングでは3つのことが複合し、すべてコンテキストに起因する。
ウィンドウが埋まり、注意力が腐る。 AnthropicのBest-practicesドキュメント自身がはっきり言っている:「Claudeのコンテキストウィンドウは素早く埋まり、埋まるにつれてパフォーマンスが低下する…Claudeは以前の指示を「忘れ」始めたり、より多くの間違いを犯したりすることがある」(Claude Code best practices)。これはClaude固有の問題ではない。「Lost in the middle」効果——モデルが長いコンテキストの中間にある情報に最も注意を払わない——はよく文書化されており、研究によるとウィンドウが完全に埋まるはるか前から劣化が始まる可能性がある。リファクタリングが進むにつれて、読み込まれたファイルやコマンド出力が同じ有限ウィンドウに積み重なり、最初の指示と宣言した不変条件を注意力の低い中間へ押しやっていく。
実効ウィンドウはラベルよりも小さい。 Cursorユーザーは常にこれに直面する:200K advertised windowのモデルでも、harnessのオーバーヘッドやsystem prompt、ツール出力を考慮すると、実際に使用できるのは~40–60K tokenに過ぎず、「3番目か4番目のステップまでに、Agentはステップワンでやったことについてのdegraded awarenessで動いている」(Morph、Augment Code on Cursor multi-file refactors)。200K tokensのソースで50ファイルにわたるリファクタリングは単純にフィットしないので、ファイル31–50はファイル1–30を忘れたAgentによって編集される。
ファイル間のコントラクトは一度に一ファイルのエディタには見えない。 リファクタリングのリスクはすべて継ぎ目に潜む:古い返り値の形を仮定していたcaller、古いフィールド名に基づいて構築されたfixture、まだ古いキーを書いているserializer。Agentはファイルを正しく編集し、ファイルBも正しく編集するが、両者間のコントラクトはどの単一ファイルも表現しない。そこに半分しか適用されていない変更と壊れた不変条件が住む。
私のような単純な改名の場合、最初の正直な質問はAgentがそもそも触れるべきかどうかだ。
変更が機械的なときはCodemodを使う
変換が構造的でルールベースの場合——シンボルの改名、呼び出し署名の変更、importの入れ替え——決定論的なAST codemoduはすべての重要な軸でAgentを打ち負かす。jscodeshiftのようなツールは抽象構文木上で動作するため、すべての一致するノードにまったく同じ変換を適用し、並行して、コードベース全体にわたって「数秒で」、2つのファイルが乖離する可能性はない(Toptal、Martin Fowler on codemods)。Agentは対照的に、大きなコードベースでは「幻覚の可能性もあり、毎回わずかに異なる結果を出し、遅くてコストがかかる」(Carlos Cuesta)。
Agentの強みを本当に活かすムーブ:Agentにcodemoduを書かせること、編集を実行させることではない。変換を説明すると、Agentが jscodeshift transformを生成し、3つのファイルでテストし、60全てに決定論的に実行する。ルールを表現するAgentの流暢さと、AST toolのルールがどこでも同一に適用されるという保証が得られる。
# AgentがtransformのAgent account-to-workspace.jsを書き、あなたが実行する:
npx jscodeshift -t transforms/account-to-workspace.js src/ --dry # プレビュー
npx jscodeshift -t transforms/account-to-workspace.js src/ # 適用
git diff --stat # 60ファイル、一つの決定論的な変換
私の改名は80%が機械的だった。正しい判断は改名にはcodemoduを使い、判断が必要な~12ファイルのみAgentを使うことだった——serializer、マイグレーション、AccountとWorkspaceが移行期間中に共存しなければならない場所。Agentを使うのは変更が意図の読み取りを必要とするとき、ルールの適用を必要とするときではない。
まず計画する、編集できないモードで
変更が本当にAgentを必要とするとき、最初のターンで編集を始めさせてはいけない。最大のレバーは探索と計画を実行から分離することで、Agentがディスクに触れる前にアプローチを確定させる。
Claude Codeでは、これはplanモード(Shift+Tab、または--permission-mode plan)だ:Agentはファイルを読み、importsを追跡し、一切の編集をせずに計画を作成し、最初にあなたが承認する(common workflows)。Aiderには同等の分離がある——再構成を計画するための /architect、その後実行するための /code (refactoring a large codebase with aider)。儀式のためではない。Agentに「直接コーディングに飛びつかせると間違った問題を解くコードが生まれる可能性があり」、2分で読む計画が一時間の編集のコストが発生する前にそれを捕まえる(best practices)。
ドキュメントはいつ気にすべきかについて明確な線を引く:「diffを一文で説明できるなら計画をスキップ。」マルチファイルリファクタリングは一文に収まらない。計画する。
大きな仕事への便利なディテール:「60ファイル全部編集する」に分解される最初の計画を受け入れてはいけない。依存関係の順序でリファクタリングするよう促す——他に何も依存しないモジュールから始めて外向きに作業する——検証するのに十分小さいフェーズに作業を分割する。実際には、Agentは一回のパスで約20–40ファイルで信頼性を保つ;それ以上は、フェーズに分割し、それぞれの間にcommitを入れて別のセッションとして実行するというのがコンセンサスだ(Claude Code multi-file refactoring walkthrough)。
独立して検証可能なステップに切り、各ステップ間にゲートを設ける
これが実際にあなたを救う部分だ。Agentが一つの区別されないスイープで行うリファクタリングには内部チェックポイントがないため、最初のエラーがその後のすべてをサイレントに汚染する。ステップに切り分けられたリファクタリング——それぞれがpassまたはfailするチェックで終わる——は、それを捕まえるゲートの先にエラーを伝播できない。
Anthropicはこれを中心に規律全体をフレームする:「Claudeは作業が完了したように見えると止まる。それが実行できるチェックなしには、「完了したように見える」が唯一の利用可能なシグナルで、あなたが検証ループになる。」pass/failを返すチェックを渡す——テストスイート、ビルド終了コード、typecheck——「ループが自閉じする」(best practices)。チェックが「Agentは完了したと思う」を「typecheckerも完了に同意する」に変換する。
以下が私が Account → Workspace 改名に使うべきだった段階的な計画だ。依存関係の順序で、各ステップの後にゲートを設けて:
PLAN: Account -> Workspace 改名。ゲートを通過した各ステップの後にCommit。
[ ] 1. src/domain/ でtype + fieldsを改名(依存先なし)。
GATE: src/domain/ でtsc --noEmit クリーン。Commit。
[ ] 2. typeをimportするdata layer(repositories, serializers)を更新。
GATE: tsc --noEmit クリーン;src/data/のunit tests合格。Commit。
[ ] 3. service/API layerのcall sitesを更新。
GATE: repo全体でtsc --noEmit クリーン;integration tests合格。Commit。
[ ] 4. test fixtures + factoriesを新しいfield namesに更新。
GATE: 完全なtest suiteがグリーン。Commit。
[ ] 5. UI components + 残りのstring referencesを更新。
GATE: tsc --noEmit クリーン;ビルド成功;e2e smokeパス。Commit。
各ステップはコンテキストに収まるのに十分小さく、決定論的なゲートで終わり、次が始まる前にcommitされる。ステップ3が90個のエラーを出せば、ステップ3で見える——ステップ1と2はすでにgitで安全——60ファイル全体で最後にではなく。Commitはゲートと同じくらい重要だ:しくじったステップは git reset 一つ先にあり、考古学プロジェクトではない。
Agentにゲート自体を実行させて出力を表示させるよう指示する。成功を主張させるのではない。「Claudeに成功を主張させるのではなく証拠を示させる:テスト出力、実行したコマンドと返ってきたもの」(best practices)。厳密に強制するには、Stop hookがチェックをスクリプトとして実行し、パスするまでターンが終わるのを拒否できる。
チェックリストをAgentが失えないファイルに保存する
上記の計画はAgentがステップ5でそれを持っている場合にのみ役立つ。会話にのみ存在するのであれば持っていないだろう——ステップ5までに早期のステップはウィンドウからエイジアウトされており、それがまさに最初に始まった失敗だ。だからチェックリストはファイルに入れる:PLAN.md またはリポジトリの REFACTOR.md、Agentはそれを読み、次の未チェックステップを行い、ゲートを実行し、ボックスにチェックを入れるよう指示される。
Anthropicは長くて徹底的な仕事に対してまさにこれを推奨する:「コードマイグレーション、多数のlintエラーの修正、または複雑なビルドスクリプトの実行」のために、Claudeに「Markdownファイル(またはGitHub issue!)をチェックリストと作業メモ帳として使わせる」(best practices)。リポジトリ内のファイルは外部メモリだ。Agentはターンごとに再読するので、計画は膨らんだコンテキストの中間で腐るのではなく、完全な注意のもとに留まる。チェック済みのボックスが実際に完了したものの耐久性のある記録だ——Agentがおぼろげに覚えていることではなく。
コンテキストの衛生管理と組み合わせる。ステップ間——またはセパレートセッションとして実行されるフェーズ間——/clear して次のステップがクリーンに始まるようにし、チェックリストと現在のステップのファイルのみをロードする。「Xがどう機能するか読んでこい」という探索はサブエージェントに委任し、読み取りが別のウィンドウで行われ、サマリーだけが返ってくるようにする。(どちらもここに独自の投稿がある——再論しない。)
コードを書かなかった検証パス
最後のゲート、ステップが完了した後:実装を見たことがないフレッシュなAgentで全diffをレビューする。レビュアーは「フレッシュなサブエージェントコンテキストで実行され、diffとあなたが与えた基準のみを見る。変更を生み出した推論ではなく、だから独自の条件で結果を評価する」(best practices)。計画に向ける:
REFACTOR.mdに対して完全なdiffをレビューするためにsubagentを使用する。各ステップの
変更が完全に適用されているか確認する——古いtypeや古いfield namesを持つファイルが
残っていないか、まだ古い形式を返しているmethodがないか。正確性や明示された要件を
壊すギャップのみを報告し、スタイルは除く。
最後の文が重要だ。ギャップを見つけるよう言われたレビュアーはそれを作り出す——「余分な抽象化レイヤー、防御的なコード、起こりえないケースのためのテスト」——だからスコープを正確性とコントラクトに限定する。そうしないと、きれいなリファクタリングを1週間のgold-platingと交換することになる(best practices)。
私が繰り返し学んでいること:Agentは大きなリファクタリングのボトルネックではない——スコープがボトルネックだ。60ファイルの改名を一括で渡せば、どんなに優れたモデルであっても、漂流し、半分しか適用されず、完了についての嘘をつく。同じ改名を5つの依存関係順のステップに切り分け、各ステップをtypecheckでゲートし、各ステップをcommitし、Agentが再読するファイルで追跡すれば——それは完了する。なぜなら、どの時点においても私はそれが保持できる以上のものを保持するよう求めなかったからだ。計画し、スライスし、ゲートし、書き留める。変更がただのルールであれば、codemoduを書いて、Agentがもっとも苦手な部分をAST任せにする。
