🧪

Harness as Code — CoDD活用ガイド #3 既存コードのバグ修正 リバース設計書で+30%、足しすぎたら全部退化

に公開

https://github.com/yohey-w/codd-dev


結論から言う

Opusに「このバグ直して」って丸投げしたら、30問中5問しか正解しなかった。パッチの当て方すら間違ってた初期状態だ。そこを直して13問。ここが「配管だけの天井」。

で、codd extractで作った設計書を一緒に渡したら17問正解になった。+30.8%。

ここまでは気持ちいい話。問題はここから。

「もっと情報を渡せばもっと上がるだろう」と思って、AI分析を足した。関数単位のコンテキストを足した。修正候補を3つ作って選ばせた。検証ループを入れた。

全部退化した。1つ残らず。

コンテキストは「渡せばいい」じゃなかった。渡しすぎると壊れる


SWE-benchって何

SWE-bench。プリンストン大学が作った「AIにバグ直させてみようぜ」ベンチマーク。GitHubの実際のIssueとPull Requestから作られている。

ルールはシンプル:

  • もらえるもの: バグレポート(Issue本文)+リポジトリ全体
  • 出すもの: 修正パッチ
  • 判定: テストが通ったら正解。部分点なし

SWE-bench Liteは300問の軽量版で、公式が「計算コスト気になるならこれで報告していいよ」と言っている。今回はそこからDjango + Astropyの30問でパイロット。


パイプライン — Opusに1回聞くだけ

#!/bin/bash
# swebench_harness.sh — SWE-benchパッチ生成パイプライン
OPUS="claude -p --model claude-opus-4-6 --tools ''"
REPO="$1"           # Djangoリポジトリのパス
BUG_REPORT="$2"     # Issue本文

# ① 修正対象ファイルを特定
TARGET=$(echo "以下のバグレポートから修正すべき
ファイルパスだけ返せ: $BUG_REPORT" | $OPUS)

# ② codd extract: 対象モジュールの設計書を静的生成
codd extract --path "$REPO" \
  --source-dirs "$(dirname "$TARGET")" \
  --output "$REPO/codd/extracted/"

# ③ 設計書+ソース+バグレポートからパッチ生成
PATCH=$(echo "設計書: $(cat "$REPO"/codd/extracted/modules/*.md)
ソース: $(cat "$REPO/$TARGET")
バグ: $BUG_REPORT
SEARCH/REPLACEブロックで修正パッチを生成せよ。" | $OPUS)

# ④ パッチ適用 → git diff を提出
python3 apply_edits.py "$PATCH" "$REPO"
cd "$REPO" && git diff

これがタイトルの「Harness as Code」だ。CoDDのコマンドとClaude CLIでパイプラインをシェルスクリプトに定義する

実際にDjangoリポジトリに対して②を実行するとこうなる:

$ codd extract --path ./django --source-dirs django/forms --output /tmp/docs/

Extracted: 9 modules from 9 files (6,120 lines)
Output: /tmp/docs/
  modules/boundfield.md  modules/fields.md  modules/forms.md
  modules/formsets.md    modules/widgets.md  ...

9モジュール分の設計書が数秒で出てくる。AI不使用、コスト0。エージェント型(テスト走らせて失敗したら修正して再テスト)は使っていない。Opusに1回だけ聞いて、出てきたパッチをそのまま投げる。一発勝負。

なぜシングルショットか。ループを回せば精度は上がるだろうけど、オレが測りたいのは「コンテキストの質がどれだけ効くか」であって「何回リトライしたか」じゃない。


codd extractが生成するもの

ソースコードを構文解析して構造を抜き出すだけ。AIは一切使わない。コスト0。1モジュール数十ミリ秒。

出力する設計書の中身:

セクション 中身
Symbol Inventory 関数・クラス・メソッドの一覧と位置
Public API 外部から呼べるインターフェース
Import Dependencies 他モジュールへの依存
Call Graph 誰が誰を呼んでるか
Inheritance クラスの継承チェーン

要するに「このモジュール、こういう構造してますよ」というカンペ。人間のエンジニアなら頭に入ってる情報を、AIにも教えてやるだけの話。


何を足したら上がって、何を足したら下がったか

効いたもの

ベースライン: 13/30(43.3%)

バグレポート + 対象ファイルのソースコード + 周辺ファイルをOpusに渡して「直して」と丸投げ。出力形式の最適化など基盤部分は事前にチューニング済み。ここまでがハーネス改善の限界。コンテキストの実験はここから。

① + CoDD extract: 17/30(56.7%) ← +30.8%、退化ゼロ

ベースラインのプロンプトに、codd extractが生成したモジュール設計書を追加。具体的にはこういうMarkdownが差し込まれる:

## Symbol Inventory
| Kind | Name | Location | Signature |
|------|------|----------|-----------|
| class | BoundField | fields.py:20 | BoundField(form, field, name) |
| method | as_widget | fields.py:89 | as_widget(self, widget=None, attrs=None) |
...

## Call Graph
| Caller | Callee | Location |
|--------|--------|----------|
| as_widget | build_attrs | fields.py:95 |
...

ソースコードと一緒に「この人たちの関係図」を渡す。+4問が新たに正解、退化ゼロ。純増

② + developer hints: 18/30(60.0%)

①に加えて、GitHubのIssueに付いていた開発者同士の議論(「これはfields.pyのas_widgetが原因では?」的なコメント)をそのまま渡した。+1。CoDD extractの+4に比べると限定的。

どっちが効いてる? — 設計書とhintsを足したり引いたり

ここで当然疑問が出る。「設計書が効いたのか、hintsが効いたのか、両方か?」

切り分けるために、設計書あり/なし × hintsあり/なし の4パターンを測った。

条件 CoDD extract developer hints 結果
ベースライン - - 13/30(43.3%)
hints only - 16/30(53.3%)
extract only - 17/30(56.7%)
extract + hints 18/30(60.0%)

CoDD extractだけで+4、退化ゼロ。 hintsだけでも+3。どっちも単体で効く。

じゃあ両方足したら+7になるかというと、ならない。+5止まり(18/30)。なぜか。extractで解けるようになった4問とhintsで解けるようになった3問が、かなり被ってるからだ。同じバグが「設計書があれば解ける」し「人間のヒントがあっても解ける」。つまり両方とも同じ"足りなかった情報"を別ルートで補っていた

ちょっと待ってほしい。hintsって何かというと、人間の開発者がIssueで「ここが原因じゃない?」「このメソッドの挙動がおかしい」と議論した結果だ。ドメイン知識を持ったエンジニアが、実際にコードを読んで、考えて、書いたコメント。

extractは? TreeSitterで構文木を舐めて構造を吐いただけ。AI不使用。コスト0。数秒。

それが人間の議論と同等以上に効いてる。

しかも決定的な差がある。hintsは「そのIssueにたまたま有益なコメントが付いてた」場合しか使えない。コメントがゼロのIssueでは効果もゼロ。extractはどのリポジトリのどのモジュールでも、いつでも、誰でも再現できる

extract hints
効果 +4(退化ゼロ) +3
コスト $0 $0(ただし人間が書いた)
生成時間 数秒 数時間〜数日(人間の議論)
再現性 どのリポでも Issueにコメントがある場合のみ
前提条件 ソースコードがある 有益な議論がある

機械が数秒で吐いた構造情報が、人間が数時間かけた議論と同じだけAIの判断を助ける。 これがオレがcodd extractを作った理由だし、CoDDの「Coherence-Driven」の意味だ。

効かなかったもの(全部退化)

ここからが本題。

施策 結果 死因
AI分析を追加(codd extract --ai 17→16 Opusの判断にSonnetが横から口出しした構図。余計な解釈がOpusの直感を狂わせた
関数単位のコンテキストだけ渡す 17→0 モジュール全体が見えないと壊滅。木を見せて森を隠す愚
修正箇所の絞り込み(バグっぽい関数とその周辺だけ渡す) 18→15 「ここ直すんでしょ?」とお膳立てしたら逆効果。余計なお世話
別のAIにパッチを検算させる(Opusが書いた修正をSonnetが「これ合ってる?」とチェック) 18→17 ダメなパッチは検知するけど、作り直したパッチも微妙。ジャッジが優秀でも選手がいない
Best-of-3(3候補作って最小diff選択) 18→15 「一番小さい変更が正解でしょ」→ 違う。正解は中サイズの変更だった

5つやって5つとも退化。


コンテキスト断捨離の法則

ここでオレが「コンテキスト断捨離」と呼んでいる法則が見えてきた。

渡したもの 結果
ソースコードだけ 13/30(配管だけの天井)
+ 開発者の議論 16/30(+3)
+ モジュール設計書 17/30(+4、退化ゼロ)
+ 設計書 + 開発者の議論 18/30(微増)
+ 設計書 + AI分析 16/30(退化)
+ 設計書 + 議論 + 修正箇所の絞り込み 15/30(退化)
関数だけ(モジュール全体を隠す) 0/30(壊滅)

コンテキストには「最適な幅」がある。 狭すぎると情報不足で壊滅、広すぎるとノイズで退化。ちょうどいいのが「モジュール全体の設計書」だった。

人間のエンジニアが先輩にバグ修正を頼むとき、「このモジュールこういう構造でさ、ここがおかしいんだよね」と説明するだろう。そのとき先輩に渡すのは関数のソースでもAIの長文分析でもなく、モジュールの地図だ。

AIも同じだった。


なぜ「足すと退化する」のか

オレの仮説。

Opusはバグレポートを読んで「こう直すべきだろう」という直感を持っている。CoDD extractの設計書は、その直感を裏付ける。「やっぱりこの構造ならこう直すのが正解だな」と確信を持てる。

ところが追加情報を足すと、Opusは迷い始める。AI分析が「ここも考慮すべき」と言うと、Opusは「確かにそうかも」と判断を揺らす。repair-sliceが「ここを直すべき」と指定すると、Opusは自分の直感より指示を優先する。

つまりコンテキストは「AIの判断を助ける」のではなく、「AIの判断を上書きする」。助けるコンテキストと上書きするコンテキストの境界が、モジュール設計書のレベルにある。

Anthropicがcontext engineeringと呼んでいるものの実践例が、まさにこれだと思っている。


30問パイロットの限界(正直に書く)

  • 30問は少ない。SWE-bench Lite全300問での追試が必要
  • Django + Astropyだけ。他リポジトリでの汎化は未確認
  • 60%は最高記録じゃない。エージェント型トップは70%超。ただしあれはテスト実行ループあり
  • シングルショットの天井。設計書を入れた後、プロンプトや渡す情報を7パターン変えて試したが全部17〜18/30。1回聞きでは60%前後が限界で、これ以上はテスト実行ループが要る
  • API料金: Claude Maxサブスク枠内で完結。従量課金ゼロ。CoDD extractもAI不使用で$0

ただ、30問でも「設計書を渡すだけで退化ゼロの+30%」はノイズじゃ説明できない。方向性は出た。


ハーネスとコンテキスト — 配管と水の質

最近「コンテキストエンジニアリング」と「ハーネスエンジニアリング」という言葉をよく見かけるようになった。この2つ、実は対立概念じゃない。

ハーネスはAIの周りに組む配管だ。CoDDのコマンドをシェルスクリプトで繋ぐ、周辺ファイルを渡す、codd extractをパイプラインに組み込む——全部ハーネス。

コンテキストはその配管の中を流れる水の質。codd extractが吐く設計書に何を載せるか、どの粒度で渡すか——これがコンテキスト。

今回の実験でわかったのは、配管をいくら磨いても水の質が変わらないと天井があるということ。

配管だけでは13/30が限界

ベースラインの13/30は、バグ修正(出力形式が壊れてた)と配管改善(周辺ファイルを渡す)で到達した。配管としてはそこそこ出来ている。でもAIが「何を知っているか」は変わっていない。

水の質を変えたら突破した

同じ配管に、codd extractの設計書を流した。モジュールの構造——誰が誰を呼んでいて、何がpublicで何がprivateか——という情報の質を変えた。13/30→17/30。

ただし水も盛りすぎると毒になる

AI分析を足した。関数単位に絞った。修正候補を3つ作った。全部退化した。コンテキストは多ければいいってもんじゃない

精度
 ^
 |                          ← 水を盛りすぎた(退化)
 |                    ●18/30 (60%)
 |              ●17/30 (56.7%) ← CoDD extract
 |         ─ ─ ─ ─ ─ ─ ─ ─ ← 配管だけの天井
 |    ●13/30 (43.3%) ← ベースライン
 +──────────────────────────→ 情報量
   少ない            多い

巷のコンテキストエンジニアリングの議論は「何を渡すか」に集中しているけど、**「何を渡さないか」**が同じくらい重要だというのがこの実験の結論。AI分析を渡さない。関数単位に絞らない。修正箇所を指定しない。設計書だけ渡して、あとは黙る。

オレはこれをコンテキスト断捨離と呼んでいる。


バグ修正だけじゃない

ここまでSWE-benchの「バグ修正」で話してきたけど、考えてみてほしい。

AIに既存コードの機能追加を頼むとき、何が必要か。「この関数に引数を追加して」と言うだけじゃダメだ。その関数を誰が呼んでいるか、publicかprivateか、継承チェーンの上に何がいるか——モジュールの地図が要る。

バグ修正と同じだ。

リファクタリングも同じ。「このクラスを分割して」と頼むなら、そのクラスの依存関係、呼び出し元、テストの所在を教えてやらないと、AIは見当違いの分割をする。

つまり**「モジュール設計書を渡すと精度が上がる」という知見は、既存コードに対するあらゆる作業に当てはまる**。バグ修正、機能追加、リファクタリング。原理は同じで、AIに「ここの構造を知ってる状態」で判断させるか、「知らない状態」で推測させるかの違いしかない。

今回はSWE-benchで数字を出せたけど、オレが本当に言いたいのはこっちだ。codd extractは「バグ修正ツール」じゃない。既存コードをAIに触らせるときの前提条件だ。

機能追加やリファクタリングでも同じ効果が出るか? 次回以降の記事で実証していきたい。


で、あなたのAIには何を渡してる?

AIに長文の分析を渡してませんか。AIに修正箇所を指定してませんか。AIに3回聞いて一番短い回答を選んでませんか。

それ、全部退化した施策なんですよ。

AIに渡すべきは「モジュールの地図」——何があって、誰が誰を呼んでいて、何がpublicで何がprivateか。それだけ。あとはAIの判断に任せる。

オレはこれを「整合性駆動」と呼んでいる。CoDDの"Coherence"はそういう意味だ。


このシリーズが初めての方はこちらから

https://zenn.dev/shio_shoppaize/articles/5fee11d03a11a1

GitHub

https://github.com/yohey-w/multi-agent-shogun

CoDD

https://github.com/yohey-w/codd-dev


今回の実験で分かったのは「AIに何を渡すか」が結果の大半を決めるということだ。プロンプトのテクニックじゃない。自分の頭の中にある設計意図を、AIが読める形にすること。そのへんの考え方は全部この本に書いた。

https://zenn.dev/shio_shoppaize/books/ai-mentoring-thinking

Discussion