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にある。
- 依頼単位でグループ化する(request-level grouping)
- 個々の行について根拠を確認できる(why)
- 依頼ごとのreview用patchへ分割する(split)
- 証拠が不十分な行は隔離する(unresolved patch)
- 分割した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との違い
事実ベースで短くまとめると、次のようになる。
| 項目 | wipwho | ai-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件)は以下で公開している。