TPL 実験の資料編
本編の記事で行った実験の資料集です。
条件の定義、依頼プロンプトの全文、採点基準、結果の表、追試するときの落とし穴をまとめています。
実験台にした karasu は公開リポジトリなので、TPL の実物(docs/test-perspectives/)も読めます。
実験環境
- 対象リポジトリ:karasu(TPL 70 本。ほかに ADR 約 250 本、仕様書、エージェント向け常設ルール群)
- エージェント:Claude Code 2.1.205 / 2.1.206 の headless 実行(
claude -p、bypassPermissions) - モデル:Claude Opus 4.8 に全ラウンド固定
- 分離:条件ごとに独立 clone を作り、試行のたびに基準コミットへ hard reset。エージェントが作る git worktree も毎回撤去
- メモリ:
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1でセッション横断の記憶を無効化 - 履歴:TPL を消した条件は orphan 履歴で作り直し、reflog の失効と gc まで実施(前回実験で削除が履歴から発掘された対策)。設計タスクのラウンドでは両条件とも履歴を単一コミットに対称化
条件定義
全ラウンド共通で 2 条件です。
- cond1(TPL あり):原状のまま
- cond2(TPL なし):
docs/test-perspectives/70 本の削除に加えて、参照導線を層別に中和
cond2 の中和は 213 ファイルに及びました。 内訳は次の 4 層です。
| 層 | 対象 | 処理 |
|---|---|---|
| 指示層 | CLAUDE.md の TPL 参照節、QA エージェント定義、常設ルール内の言及、PR / Issue テンプレートの「TPL impact」節 | 節ごと削除または言及の中和 |
| 仕組み層 | TPL 採用を記録した ADR、TPL 検証の CI ワークフローと git hook | 削除 |
| 内容層 | 仕様書の「Related TPLs」相互リンク、受け入れテスト台帳と ADR 本文の TPL-ID 言及 | スクリプトで機械除去 |
| コード層 | ソースとテスト内の TPL-ID コメント(約 60 ファイル) | 意図的に残置(既知の漏れ。結果表の補足を参照) |
中和後に、コード層を除く 3 層で「TPL」「test-perspectives」の残存 0 を grep で検証し、2 回実行で結果が変わらないこと(冪等)も確認しています。
ラウンド一覧
| ラウンド | 型 | 問い | run 数 | 主結果 |
|---|---|---|---|---|
| 1 | レビュー | エントリ欠落(omission)型の埋め込みバグを検出するか | 6 | 両条件 3/3 検出(天井) |
| 2 | レビュー | 値の不整合型なら差が出るか | 10 | 両条件 5/5 検出(天井)。ターン数のみ傾向差 |
| 3 | 実装 | 新 kind 追加で遠い表現まで自発的に配線するか | 6 | 全 run が配線(天井)。ターン上限の較正ミスあり |
| 4 | 設計 | DesignDoc と受け入れ基準の観点漏れに差が出るか | 10 | 平均 5.2 対 4.2。弁別項目は 5/5 対 0/5 |
| 5 | 設計 | 別題材(セッション復元)で再現するか | 6 | 平均 5.7 対 4.0 で再現。境界線の直接観察 |
埋め込みバグの作り方(ラウンド 1・2)
的にした観点は TPL-20260519-02(同一語彙の二重表現 drift)です。
新しい deploy kind vm を追加する変更(19 ファイル、約 85 行)を機能追加 PR の体裁で作りました。
AST、パーサ、スタイル定義(ダークとライトの両テーマ)、アイコン、LSP、仕様書の生成テーブル、最小限のテストまで配線してあります。
- ラウンド 1(omission):draw.io エクスポータの kind 別スタイル表にだけ
vmを追加しない - ラウンド 2(値の不整合):エントリは追加するが、色を隣の kind(lambda)からコピペした値にする。SVG 側の
vmは紫系、draw.io 側だけ黄系になり、検索では「配線済み」に見える
どちらも型チェック、lint、単体テスト約 3,700 本、仕様書テーブルの生成チェックがすべて通ることを確認してから使っています。
依頼プロンプト全文
いずれも TPL、観点、波及先への手がかりを入れない形にしています。
レビュー(ラウンド 1・2 共通)
直前のコミット(HEAD)は、新しい deploy kind `vm` を追加する変更です。
この変更を PR として merge してよいかレビューしてください。
問題があれば、どのファイルの何が問題かを具体的に指摘してください。
コードの修正は行わず、レビュー結果の報告だけしてください。
実装(ラウンド 3)
karasu に新しい deploy kind `vm` を追加してください。
仮想マシンイメージ(EC2 AMI や GCE image など)を表すノード種別で、既存の
deploy kind(oci, lambda など)と同格の first-class な kind として扱えるように
してください。実装・テスト・必要なドキュメント更新まで、この変更単体で PR
として merge できる完成度に仕上げてください。リポジトリの既存の規約・作法に
従ってください。
設計 1:コンテナの in-place 展開(ラウンド 4)
karasu に「コンテナの in-place 展開」機能を追加したい。
system view で 1 つのコンテナ(system / domain)だけを inline で展開し、その内部
要素を、畳まれたままの兄弟コンテナと同じ図の中に表示する。境界を越えるエッジも
見えるようにしたい。
この機能の DesignDoc と受け入れ基準(acceptance criteria)を
`docs/design/in-place-expand.md` に書いてください。実装はしないでください。
リポジトリの既存の設計文書の流儀に従ってください。
設計 2:セッション復元(ラウンド 5)
karasu に「プロジェクトごとのセッション復元」機能を追加したい。
アプリを開き直したとき、プロジェクトごとに前回の状態 — 開いていたファイル、
アクティブな view、drill 先、ハイライト中のノード、パネルの開閉状態 — を
復元して、作業を中断したところから再開できるようにしたい。
この機能の DesignDoc と受け入れ基準(acceptance criteria)を
`docs/design/session-restore.md` に書いてください。実装はしないでください。
リポジトリの既存の設計文書の流儀に従ってください。
採点の方法
レビュー(ラウンド 1・2)
条件を伏せた LLM(Claude Opus 4.8、ツールなし)に、埋め込みバグの内容と判定基準を渡して binary で判定させました。 判定基準は「該当ファイルの該当箇所を具体的に指摘しているか」で、drawio に言及しただけでは検出と数えません。 全判定について、根拠引用を原文と機械的に突き合わせて裏を取っています。
採点対象はエージェントの全応答テキストの連結です。 最終応答だけを渡すと、「前のメッセージの所見は維持」という締め文だけの run で本体を取り逃し、誤判定が起きました(実際に一度起きて訂正しています。落とし穴の節を参照)。
実装(ラウンド 3)
成果 diff を、正解の配線先 15 箇所(AST からエクスポータ、VS Code 拡張まで)のチェックリストと機械照合しました。 LLM の判定は使っていません。
設計(ラウンド 4・5)
該当 TPL のチェックリストから起こした 6 項目について、条件を伏せた LLM に「仕様・受け入れ基準として明示的に規定しているか」を判定させました。 「触れている」だけでは不十分で、実装者が迷わない規定になっていることを yes の基準にしています。 弁別項目(差が出た項目)は全件、根拠引用を原文と突き合わせて検証しました。
ラウンド 4 の 6 項目は次のとおりです。
- 同時展開数、表示要素数の明示的上限
- 初期状態が collapsed であることと、畳み直す操作(戻りパス)
- 展開中の要素が二重描画されず、脱落しないこと(exactly-once)
- 畳んだ兄弟の内部要素を端点とするエッジの扱い(集約規則)
- 退化ケース(空コンテナ、全部展開など)の挙動
- 展開による図の巨大化、視覚密度への対処
ラウンド 5 の 6 項目は次のとおりです。
- アプリ初期化が復元を巻き戻す問題への対処(適用順序)
- 復元対象フィールドの列挙と全フィールド対称の復元
- 復元の再適用が一度きりであること
- 壊れた保存データと「保存なし」の区別
- 復元失敗時の挙動(fail-closed)
- URL ハッシュなど他の状態ソースとの優先順位
結果表
レビュー検出(ラウンド 1・2)
| ラウンド | バグの型 | cond1(TPL あり) | cond2(TPL なし) |
|---|---|---|---|
| 1 | エントリ欠落 | 3/3 検出 | 3/3 検出 |
| 2 | 値の不整合 | 5/5 検出 | 5/5 検出 |
ラウンド 2 の平均ターン数は cond1 が 23.6、cond2 が 31.2(平均コストは 1.26 ドルと 1.43 ドル)でした。 検出率に差はなく、探索の長さだけに傾向差が出ています。 ただし分散が大きく、n = 5 では傾向どまりです。
補足を 2 つ。
cond1 は全試行で TPL を読みに行っており、参照導線は機能していました。
また cond2 では、コード層に残した TPL-ID コメントを手がかりに docs/test-perspectives/ を探しに行って空振りする挙動が 5 試行中 2 回ありました。
残置したコメントは内容を与えませんが、探索は誘発します。
実装の網羅(ラウンド 3)
全 6 run(両条件)が draw.io エクスポータまで自発的に配線しました。 VS Code 拡張側もほぼ全 run が配線しています。 表現間の一致を固定する parity テストを書いた run は 0/6 でした。
ただしこのラウンドはターン上限 80 の設定ミスで 6 run 中 4 run が打ち切られており、「テストを書かなかった」のか「書く前に切られた」のかを弁別できません。 配線の網羅(打ち切り前に完了していた)だけを結果として採用しています。
設計 1:in-place 展開(ラウンド 4、各条件 5 試行)
| 項目 | cond1 | cond2 |
|---|---|---|
| 同時展開の上限 | 5/5 | 5/5 |
| collapsed 初期状態と戻りパス | 5/5 | 5/5 |
| exactly-once(二重描画と脱落の禁止) | 5/5 | 0/5 |
| エッジ集約 | 5/5 | 5/5 |
| 退化ケース | 5/5 | 5/5 |
| 視覚密度 | 1/5 | 1/5 |
| 平均スコア | 5.2/6 | 4.2/6 |
exactly-once の 5/5 対 0/5 は Fisher の正確検定で p ≈ 0.004(片側)。 原文照合では、cond1 の 5 文書が該当規定を 2〜5 回明記し、cond2 の 5 文書は言及 0 回でした。 文書の分量は両条件で同等(165〜307 行)です。 視覚密度の項目は judge の再実行で判定が揺れた境界事例だったため、本編の主張からは外しています。
cond1 は 5 試行すべてが該当 TPL(TPL-20260624-02)を読んでいました。 次のラウンド 5 でも同様で、cond1 の 3 試行すべてが該当 TPL 2 本(persistent store と restore state)を読んでから設計に入っています。
設計 2:セッション復元(ラウンド 5、各条件 3 試行)
| 項目 | cond1 | cond2 |
|---|---|---|
| 復元の適用順序(初期化の巻き戻し対処) | 3/3 | 3/3 |
| フィールド対称性 | 3/3 | 2/3 |
| 再適用の一度きり性 | 3/3 | 1/3 |
| 壊れたデータと不存在の区別 | 2/3 | 0/3 |
| fail-closed | 3/3 | 3/3 |
| 他の状態ソースとの優先順位 | 3/3 | 3/3 |
| 平均スコア | 5.7/6 | 4.0/6 |
「復元の適用順序」が両条件とも 3/3 なのは偶然ではありません。 この観点は過去のバグ修正の際に、該当 hook のコードコメントとして詳しく書き残されており、全 6 run(cond2 を含む)がそのコメントに到達していました。 コードに書かれた観点は TPL なしで拾われ、コードに書かれていない観点(一度きり性、壊れたデータの区別)だけが TPL の有無で分かれています。 本編の境界線の主張は、この対比が最も直接的な証拠です。
追試するときの落とし穴
前回の資料編の 5 つに加えて、今回新たに踏んだ問題です。
- 最終応答だけを採点対象にすると本体を取り逃す。 エージェントの最終メッセージが「前のメッセージの所見は維持」という締め文だけの run があり、これを不検出と誤判定した。全応答テキストの連結を採点対象にする
- ターン上限は暴走ガードのつもりでも測定を歪める。 実装級のタスクに上限 80 は足りず、6 run 中 4 run が打ち切られた。テストは作業の終盤に書かれるため、後半の行動比較が不能になる。設計タスクでは上限を 150 に上げ、全 run が自然完走した
- git は履歴、reflog、tag、ブランチの 4 経路で漏れる。 orphan 化と reflog の失効だけでは足りなかった。深掘り fetch で付いてきた tag が旧履歴(TPL 入り)を gc から守っていたためで、ref をすべて棚卸しして消す
- 条件間で git 履歴の深さを揃える。 片方だけ実履歴を持つと、エージェントが過去の類似コミットを設計図として使える(実装ラウンドで実際に起きた)。設計ラウンドでは両条件とも履歴を単一コミットに対称化した
- コード層の ID コメントは消さなくても探索を誘発する。 残置は「内容の漏れ」にはならないが、行動には影響する。到達ログで経路を分類できるようにしておく
- 実 Issue を題材にするなら本文の観点名指しを確認する。 候補にした Issue の一部は本文が TPL や severity 指針を名指ししており、そのままでは条件差を消してしまう。お題は名指しのない形に合成し直した
ランナーの骨子
前回と同じ構造です(条件 clone、試行ごとのリセット、headless 実行)。 今回特有なのは cond2 の構築で、中和スクリプトの適用後に orphan 履歴で作り直しています。
# cond2: 中和 + 履歴の作り直し(痕跡を残さない)
git clone https://github.com/kompiro/karasu "$BASE/cond2" --depth 1
bash neutralize-tpl.sh "$BASE/cond2" # 213 ファイルの層別中和 + 残存 grep 検証
cd "$BASE/cond2"
ROOTMSG=$(git log -1 --format=%s)
git checkout --orphan clean-main
git add -A && git commit -m "$ROOTMSG"
# 落とし穴 3: 履歴・reflog・tag・ブランチの 4 経路をすべて塞ぐ
git branch -D main && git branch -m main # 旧 main が旧履歴を守る
git remote remove origin # remote-tracking ref も同様
git tag -l | xargs -r git tag -d # 深掘り fetch 由来の tag も同様
git reflog expire --expire=now --all && git gc --prune=now
# 落とし穴 4: 設計ラウンドでは cond1 も同じ手順で orphan 化し、履歴深度を対称にする
# 各試行: 基準へ戻して headless 実行(前回と同じ)
clean_worktrees
git reset --hard "$ROOT" && git clean -fdx -e node_modules
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 \
claude -p "$(cat prompt.md)" \
--model claude-opus-4-8 \
--permission-mode bypassPermissions \
--max-turns 150 \ # 設計ラウンドの値。レビューは 40、実装は 80(落とし穴 2 を踏んだ値)
--output-format stream-json --verbose > "$OUT/stream.jsonl"
TPL への到達判定は transcript から機械抽出しています。
Read ツールのパスだけでなく、Bash コマンド文字列(ls や cat 経由の閲覧)も照合対象にしました。
レビュー系の run では TPL の閲覧がほぼすべて Bash 経由で、Read ツールだけを見ると到達 0 に見えるからです。