Claude CodeとCodexの変更が混ざった。未commitのdirty treeを依頼ごとに分けるwipwho

導入

Claude CodeとCodexを並行して使っていると、あるいはその合間に自分でも少し手を入れていると、git status を見たときにdirty treeへ複数の依頼の変更がまとめて積み上がっていることがある。

git diff を見れば「何が変わったか」はわかる。しかし「この変更はどの依頼に対する応答としてreviewすべきか」は、commitを分けていない限りすぐには追えない。commitしてしまえばcommit単位の粒度は手に入るが、それはレビューを終えたあとの話で、レビュー前にまさに欲しい情報ではない。

何が困るのか

例えば、次のようなdirty treeを考える。


$ git status
 M src/total.js     # 依頼A: 入力バリデーションを追加
 M src/format.js    # 依頼B: 通貨フォーマッタを追加
 M notes.md         # 手動編集: リリースメモ

3つのファイルが同時に変更として並ぶが、それぞれ別の依頼(または人間の手編集)に由来する。commit粒度のprovenanceは、まだcommitしていない今の状態には使えない。かといって全部まとめてレビューすると、無関係な変更が同じレビュー単位に混ざってしまう。

wipwho

wipwhoは、ローカルに残っているClaude Code / Codexのログ(~/.claude/projects、~/.codex/sessions)と、現在のGit working treeを突き合わせ、未commitの変更行を「どの依頼が書いたと思われるか」でグループ化するローカルCLIツールだ。証拠が十分でない行は、無理に依頼へ割り当てず、AMBIGUOUSやNO AGENT TRACEとして明示的に残す。

attributionだけではない

ログから変更行をAI agentに紐づけること自体は、ai-blameのような既存ツールでもできる。wipwhoの主眼はそこではなく、次の一連のworkflowにある。

  1. 依頼単位でグループ化する(request-level grouping)
  2. 個々の行について根拠を確認できる(why)
  3. 依頼ごとのreview用patchへ分割する(split)
  4. 証拠が不十分な行は隔離する(unresolved patch)
  5. 分割したpatchを再適用して、元のworking treeと内容が一致することをhash検証する

「既存ツールより優れている」という話ではなく、attributionの先にある分割とreview workflowを扱っている、という違いになる。

使い方


wipwho

未commitの変更行のうち、依頼へ紐づけられたものをグループ単位で一覧表示する(タイトル/confidence/agent/session/対象ファイル/resumeコマンド)。紐づけられない行は AMBIGUOUS と NO AGENT TRACE としてまとめて表示される。


wipwho why src/format.js:5

特定の1行について、confidence/agent/session/根拠となった証拠フィールド/元の会話を再開する claude –resume / codex resume コマンドを表示する。


wipwho split --out <new-directory>

依頼ごとに1つのGit patchと、証拠が確認できなかった変更をまとめた unresolved patch を書き出す。commitはこのツールが代わりに実行するわけではなく、commit-plan.md / .json として提案が出力されるだけだ。

分けられない変更

wipwhoは3段階のラベルを返し、そのうち依頼への帰属を主張するのは1段階だけだ。

ラベル意味
ESTIMATED / MEDIUM変更行の内容がagentのtool呼び出しの結果と一致し、成功したtool result、変更に先行する依頼、tool呼び出しの時間窓に収まる保存時刻がそろっている
AMBIGUOUS / LOW候補となる依頼が複数ある、tool resultが未確認、保存時刻が時間窓の外、時刻だけが根拠、などのいずれか。どの依頼にも割り当てない
NO AGENT TRACE / NONE参照可能なログの範囲で、使える証拠が見つからない

NO AGENT TRACE は「人間が書いた」という意味ではない。スキャン対象の時間窓外の編集、未対応のtool、動的に組み立てられたpatch、ログの欠落やローテーションも同じラベルに入る。v0.1は HIGH を一度も出さない。人間がagentと同じ内容を同じtool呼び出しの時間窓内に書き直した場合、これは原理的に区別できないため、最良でも MEDIUM 止まりになる。

patchへ分ける

README.mdの手順に沿って、公開repoの node demo/run-demo.cjs を今回改めて実行した結果は次のとおりだ(デモは使い捨てのGitリポジトリと使い捨ての HOME を用意し、実物のCLIをその上で動かす。実マシンの ~/.claude / ~/.codex は一切読み書きしない)。


wipwho v0.1 · 4 dirty files · estimated provenance

Implement (summary withheld) [MEDIUM] · claude · request-e27e4c873007
  src/total.js:2  +1 -0

Implement (summary withheld) [MEDIUM] · codex · request-6834df4e51dd
  src/format.js:4-9  +6 -1

NO AGENT TRACE [NONE]
  notes.md:2  +1 -0

AMBIGUOUS [LOW]
  src/legacy.js:2  +1 -1

split –out を実行すると、Claude Code由来の依頼patch・Codex由来の依頼patch・unresolved patchの計3つに分かれる。


Verified 3 sequential patches; 4/4 file hashes match.
Omitted files: 0. Omitted/extra changed lines in included files: 0/0.

最後に内容一致を確認する

split後、独立した検証スクリプト(test/verify-export.cjs)が、出力された3つのpatchを別のクローンへ順番に適用し、最終的なファイル内容をSHA-256で比較する。今回の再実行では4ファイルすべてが一致した。


"patches": [
  { "patch": "01-request-e27e4c873007.patch", "apply": "PASS" },
  { "patch": "02-request-6834df4e51dd.patch", "apply": "PASS" },
  { "patch": "03-unresolved.patch", "apply": "PASS" }
],
"hashes": [ … 4/4 match: true … ]

ここで重要なのは、hashが一致することと、attributionが正しいことは別の主張だという点だ。hash一致が証明しているのは「分割したpatchを再適用すると、元のworking treeのバイト列を再構成できる」ことだけであり、「誰が・どの依頼がその行を書いたか」の正しさは、ESTIMATED / AMBIGUOUS / NO AGENT TRACE のラベルの話として別に扱われる。

Windows改行問題

開発中、実際に見つかったバグの1つがこれだった。core.autocrlf=true の環境で、実際には1ファイルしか変更していないのに、Git側の判定に引きずられて追跡対象の21ファイルすべてが変更候補として扱われてしまうケースがあった(evidence/line-ending-validation.json のcase B)。原因は、ソース側のGit設定を無視して core.autocrlf=false を強制していたこと、そしてraw HEADのLF blobと正規化済みdiff座標・CRLFの実ファイルバイト列を、checkoutの表現を経由せずに混ぜて扱っていたことだった。

公開版では、次の4パターンを実際の git diff / checkout出力で検証済みだ。

  • core.autocrlf=false
  • core.autocrlf=true
  • .gitattributes の text eol=lf
  • .gitattributes の text eol=crlf

任意・一貫性のない改行混在や、カスタムGit filter(clean/smudgeや ident、working-tree-encoding)は対象外で、UNSUPPORTED_CHECKOUT_FILTER 等として除外表示される(黙って誤動作はしない)。

ai-blameとの違い

事実ベースで短くまとめると、次のようになる。

項目wipwhoai-blame
出力の単位依頼(1プロンプトにつき1グループ)行 / ファイル
未帰属の扱いAMBIGUOUS / NO AGENT TRACE として明示未帰属として - 表示
レビュー用の出力split が依頼ごとのpatch + unresolved patchを出力し、byte hashで自己検証patch/export相当のコマンドはなし
再構成チェックあり(独立した再適用 + hash比較)該当なし(splitステップがない)

どちらが優れているという話ではなく、workflowと出力の形が違う、という比較にとどめている。

制約

  • 対応しているのは静的な Edit / Write / MultiEdit / apply_patch 呼び出しまでで、条件分岐・未await・動的に組み立てられた exec patchは対象外
  • 構文チェックやテストの正しさの確認はしない。非agentの人間の著者を特定することも、ログに残っていない履歴を復元することもしない
  • renameはdelete + addとして扱われ、rename前後をまたいだ行の帰属推定はしない
  • unmerged状態や、同じパスでのstaged deletionとuntrackedファイルの重複は非対応
  • ログが大きいとウォームな状態でも数秒かかり、初回実行や –no-cache はさらに時間がかかる

GitHub

ソースコードとテスト(記載で51件)は以下で公開している。

https://github.com/iwadjp/wipwho