AIにリポジトリを渡して「これを直して」と頼むと、ファイルは変更される。
でも、それで作業が終わったことにはならない。テストを実行していないかもしれないし、テストは通っていても要件を満たしていないかもしれない。途中で失敗した時に、どこまで進んだのかも分からなくなる。
このあたりを毎回その場しのぎで扱うのが面倒になって、小さなコーディングハーネスを作り始めた。
まずはモデルを賢くしない
最初から実際のモデルにつなぐと、失敗した理由がモデルなのか、ツール呼び出しなのか、検証処理なのか分からなくなる。
そこで、固定した応答を返す Fake Model で一通りの流れを通すことにした。
契約を読む
↓
隔離した worktree を作る
↓
探索してパッチを当てる
↓
許可された検証を実行する
↓
失敗したら原因を渡して修復する
↓
レビュー用のレポートを残す
足し算のような小さいフィクスチャでも、パッチが適用され、検証が走り、結果がレポートに残れば、ハーネスの縦切りとしては確認できる。
この順番にしておくと、後から実際のモデルやプロバイダーを差し替えても、最低限の完了条件は変わらない。
元のリポジトリを触らない
モデルに作業をさせる時、元の作業ツリーを直接渡すのは少し怖い。
今回は Git の worktree を使い、1回の実行ごとに専用の作業場所を作るようにした。探索、編集、テストはその中だけで行う。元のリポジトリには、最後に人が確認してから変更を戻す。
パッチを受け取るところでも、いきなり適用せず git apply --check を先に通す。パスの ..、シンボリックリンク経由の脱出、秘密ファイルへのアクセスも止める。
ここはモデルの善意に任せない方がよさそうだった。モデルが悪いというより、失敗した時の影響範囲を小さくしておきたい。
検証が通るまで成功にしない
最初の実装では、モデルの最終メッセージを見て成功扱いにしそうになった。
それだと「直しました」と返ってきただけで成功になってしまうので、実行状態を次のように分けた。
- 探索中
- 編集中
- 検証中
- 失敗の診断中
- 修復中
- レビュー待ち
- 完了
検証を通過していない実行は、どれだけ自信ありげな文章を返しても完了にならない。失敗した場合は、検証結果を次の修復ループに渡す。ただし無限に直し続けると別の問題になるので、試行回数とトークン、実行時間に上限を置いた。
途中の状態はチェックポイントに保存する。プロセスが落ちても、実行中だった場所から再開できるようにした。再開時に同じ編集を最初からやり直さないで済むだけでも、ログを追う負担がかなり減る。
プロバイダーを混ぜない
実際のモデルを使う段階では、サブスクリプション経由のバックエンドと OpenAI 互換のプロバイダーを、名前付きプロファイルとして切り替えられるようにした。
認証情報そのものは設定ファイルやトレースに保存しない。子プロセスへ渡す環境変数も必要なものだけにし、APIキーや Bearer トークンらしい文字列はログへ書く前に伏せる。
モデルを切り替えること自体よりも、認証・課金の経路と、リポジトリを変更する経路を分けて考えられるのが良かった。
CLIから先に使う
ブラウザ画面を作る前に、対話型 CLI と非対話の実行コマンドを用意した。
契約を確認
実行
検証結果を表示
失敗したら再開または修復
レポートを読む
実行ごとのレポートやトレースを .ri/runs に置く形にして、何を考えていたかではなく、何を実行して何が確認できたかを追えるようにした。
パッケージ化した tarball を一時ディレクトリへインストールするテストも入れた。開発ディレクトリでは動くのに、配布物にファイルが足りない、というありがちな問題を先に見つけたかった。
おわり
AIコーディングの難しさは、パッチを書く部分だけではなさそうだった。
どの範囲を触らせるか、何を検証したら完了なのか、失敗した時にどこから再開するのかを先に決めておかないと、モデルの出力を眺めるだけになってしまう。
まだ実際の大きなリポジトリで万能に動くものではないけど、「モデルが成功と言った」ではなく「隔離された場所で必須の検証を通った」を完了条件にできたのは良さそう。
今なら、コード修正だけでなく、ドキュメント生成や依存更新のような定型作業にも同じ枠を使えそうだと思っている。