作成日: 2026-06-11
更新日: 2026-06-12
対象 issue: #69 Claude CODE が git diff を多用するのだけど、butler でどうにかならないか。
この issue の目的は、単に git diff を禁止することではない。
Brain が git status / git diff / git log などを直接呼ぶ背景には、長い作業やコンテキスト圧縮によって「自分が何をしていたか」を見失い、git から作業文脈を復元しようとする構造的な問題がある。
したがって Butler 側でやるべきことは、raw git 出力を別ツール名で返すことではなく、Brain が必要とする作業文脈を低トークンで返すことである。
設計方針:
work_observe() は軽量な現在地確認・次アクション誘導・コンテキスト回復の入口にするwork_observe() は git status の代替であり、git diff の代替にはしないwork_diff() に明示的に委譲するrecent_records と active_issue で回復するgit は hook で止め、Butler の語彙へ誘導するButler は Claude Code(Brain)のトークン消費を抑えるために作られた作業記録システムであり、work_* ツール群はソース管理ツール全般の抽象レイヤーである。Brain は git の詳細を知らずに Butler の語彙だけで作業できることが設計上の目標。
しかし Claude Code はシステムプロンプトや訓練による習性として、git diff / git status / git log などを直接呼ぶ行動をとる。これはコミット時に限らず、ソースコード解析・状態把握のあらゆる場面で発生する。
Brain が git を呼ぶ理由は「とりあえず情報を集める癖」だけではない。長いセッションでは、Brain が自分の作業内容や判断理由をコンテキスト圧縮で失う。
その結果、Brain は次のような目的で git を呼ぶ。
このとき Brain が本当に必要としているのは、多くの場合 raw diff ではなく、作業文脈である。
work_diff を追加しても、Brain は work_diff と git diff を両方呼ぶ可能性があるgit diff だけをブロックしても、git status / git log / git show で迂回する2フェーズがセットで機能する。どちらか片方では解決しない。
Brain が git コマンドを呼ぼうとする
↓
(1) hook が git コマンド全般をブロック
+ 「代わりに使うべき Butler ツール名」をメッセージで案内
↓
(2) Brain が Butler ツールを呼ぶ
↓
Butler が作業文脈に変換した情報を、必要な粒度だけ返す
この構造で重要なのは、(2) の Butler ツールが git の薄いラッパーに留まらないこと。Brain-facing tool は「git コマンド相当」ではなく「作業記録システムの意味」を返す。
git コマンド全般(サブコマンド問わず)。
git diff だけを止めても、Brain は git status / git log / git show などに流れる可能性があるため、入口は git 全体で止める。
hook は単純な git status だけでなく、シェル上で git が前置修飾や別コマンドの後ろに現れるケースも検出する必要がある。
ブロック対象に含める例:
git status
cd /tmp && git status
GIT_PAGER=cat git log
env git diff
sudo git commit
time git push
echo foo | git commit -F -
echo git && git status
git という文字列が単なる引数や表示文字列に出るだけのケースは、実行コマンドとしての git と区別する。
単純に「git は使えません」では Brain が途方に暮れる。「代わりに使うべき Butler ツール名」を明示することが Butler への誘導の導線になる。
ブロックメッセージは git_equivalents メタデータ(後述)の逆引きで自動生成する。
git status がブロックされました。代わりに work_observe() を使ってください。
git diff がブロックされました。代わりに work_diff() を使ってください。
git log がブロックされました。代わりに work_history() を使ってください。
git branch ... は専用の Butler ツールがないため work_vcs(["branch", ...]) を使ってください。
git コマンドを全部個別実装するのは現実的でない。ちゃんと Butler として意味づけするものと、薄皮ラップで済ませるものを分ける。
Butler としての意味と抽象化を持つ Brain-facing tool。
| git コマンド | Butler ツール | 方針 |
|---|---|---|
git status |
work_observe() |
実装済み。返値を recent_records / active_issue 中心に再設計する |
git diff |
work_diff(path?, level?, record_id?) |
Phase 1 候補へ格上げ。差分確認を明示的な行為にする |
git show |
work_diff(record_id=...) |
特定記録の詳細確認として扱う |
git log |
work_history(n?, path?) |
#69 の raw git log 代替として最小実装に含める |
git commit |
work_record() |
#56 で対処中 |
git push |
work_publish() |
実装済み |
git branch / git stash / git blame / git remote / git fetch など、Butler として深く意味づけする必要はないが直接 git を呼ばせたくないコマンド群を一つの汎用ツールで受け付ける。
work_vcs(args: list[str])
work_vcs は「proper tool がある操作」を受け付けない。proper tool がある場合は、そのツールへの誘導エラーを返す。
work_observe() の再設計work_observe() は、Brain がセッション開始時または迷った時に呼ぶ軽量な入口である。
返すべきものは raw git の詳細ではなく、Brain が次の判断に進むための作業文脈である。
責務:
required_action として返すavailable_next として返す責務ではないこと:
diff_summary 自動生成案は撤回する以前の実装案では、work_observe() が git diff --stat 相当の diff_summary を自動生成する方針だった。
この方針は撤回する。
理由:
work_observe() が重くなると、状態確認のたびに不要な情報を返すことになるwork_diff() を明示的に呼べばよいrecent_records直近の work_record メッセージを返す。Brain が「何をしたんだっけ?」を低トークンで回復するための主フィールド。
{
"recent_records": [
{
"id": "20260612-001",
"message": "work_observe の返値設計を見直した",
"issue_ids": [69],
"published": false,
"created_at": "2026-06-12T10:20:00+09:00"
}
]
}
件数は固定の小さい値をデフォルトにする。詳細な履歴検索が必要な場合は work_history() へ委譲する。
active_issuedefault_issue: 69 のような番号だけでは、Brain の文脈回復には弱い。Brain-facing には番号とタイトルを含めて返す。
{
"active_issue": {
"number": 69,
"title": "Claude CODEがgit diffを多用するのだけど、butlerでどうにかならないか。"
}
}
default_issue は互換性や内部状態として残してよいが、Brain が読む主フィールドは active_issue とする。
changed_fileschanged_files は git status 代替として残す。
ただし、返すのはファイル一覧に留める。patch / stat / 行数などは含めない。
required_action と available_nextrequired_action は「次に必須の操作」に限定する。
diff 確認は必須ではないことが多いため、work_diff() は原則として required_action ではなく available_next に置く。
{
"work_state": "dirty",
"active_issue": {
"number": 69,
"title": "Claude CODEがgit diffを多用するのだけど、butlerでどうにかならないか。"
},
"changed_files": [
"docs/検討用/69_git_command_interception_実装案.md"
],
"recent_records": [
{
"id": "20260612-001",
"message": "work_observe の返値設計を見直した",
"issue_ids": [69],
"published": false,
"created_at": "2026-06-12T10:20:00+09:00"
}
],
"required_action": {
"tool": "work_record",
"reason": "未記録の変更があります",
"required_args": ["message"]
},
"available_next": ["work_record", "work_diff", "work_history"]
}
work_diff の仕様work_diff() は、Brain が変更内容を明示的に確認したい時だけ呼ぶツールである。
work_observe() が自動で diff を返さない代わりに、詳細確認の入口として work_diff() を用意する。
work_diff(path?: string, level?: "stat" | "full", record_id?: string)
| 呼び出し方 | 対応する git 操作 | 返す内容 |
|---|---|---|
work_diff() |
git diff --stat |
未記録変更の概要。デフォルトは短く安全にする |
work_diff(path="src/foo.py") |
git diff -- src/foo.py |
指定ファイルの未記録変更 |
work_diff(level="full") |
git diff |
未記録変更の詳細 |
work_diff(record_id="xxx") |
git show <hash> 相当 |
特定 work_record の変更内容 |
level="full" はトークン消費が大きくなり得るため、必要な場合だけ使う。
work_history の仕様work_history() は、直近数件では足りない場合に過去の作業記録を探すツールである。
work_observe() の recent_records で通常のコンテキスト回復はある程度できるが、hook / work_vcs が git log をブロックする以上、代替導線として work_history() は #69 の最小実装に含める。
work_history(n?: int, path?: string)
| 呼び出し方 | 対応する git 操作 | 返す内容 |
|---|---|---|
work_history() |
git log --oneline |
直近の work_record 一覧 |
work_history(n=10) |
git log --oneline -10 |
直近 N 件の work_record 一覧 |
work_history(path="src/foo.py") |
git log --oneline -- src/foo.py |
指定ファイルに関係する work_record 一覧 |
返値には record_id を含める。これにより、必要に応じて work_diff(record_id=...) へつなげられる。
work_vcs の仕様work_vcs(args: list[str])
git_equivalents に登録されていないコマンドは実行して結果を返すgit_equivalents に登録済みのコマンドは受け付けず、proper tool へ誘導するエラーを返す{
"status": "error",
"error_code": "use_proper_tool",
"summary": "このコマンドには専用の Butler ツールがあります",
"hint": "work_diff() を使ってください"
}
work_vcs を Brain-facing MCP に公開するか、内部ツールとして隠すかは未決定。
git_equivalents 動的ブロックリスト機構work_vcs のブロックリストや hook の誘導メッセージを手動管理すると、proper tool 追加時に同期漏れが発生する。
各 work_* ツールが git_equivalents メタデータを宣言し、work_vcs と hook が起動時に収集・逆引きする。
@butler_tool(git_equivalents=["status"])
def work_observe(...): ...
@butler_tool(git_equivalents=["diff", "show"])
def work_diff(...): ...
@butler_tool(git_equivalents=["log"])
def work_history(...): ...
利点:
git_equivalents を宣言するだけでよいwork_vcs のブロック対象が自動更新されるhook + Butler ツールの整備に加えて、Brain への明示的な誘導も必要。
状態確認や次の作業判断には work_observe() を使ってください。
diff を確認したいときは work_diff() を使ってください。
作業履歴を確認したいときは work_history() を使ってください。
git コマンドを直接呼ぶことは Butler プロジェクトでは禁止されています。
同様に git 直接呼び出し禁止と Butler ツールへの誘導を明記する。
依存関係とトークン削減効果を考慮した順序。
BWR 仕様書の Phase 1 として確定させる範囲は、原則として work_observe() の返値再設計と work_diff() の追加までとする。
2026-06-13 時点で、追加実装として git_equivalents backend metadata、work_vcs(args)、hook helper check_git_command、CLI butler2-git-hook を実装した。Claude Code の .claude/settings.json には butler2-git-hook を PreToolUse hook として設定済み。
work_history は #69 の raw git log 代替として最小実装に含める。通常のコンテキスト回復は work_observe() の recent_records で扱い、直近数件では足りない場合だけ work_history(n?, path?) へ進む。LLM 要約、semantic search、issue 横断検索、published 判定の精密化は Phase 2 候補とする。
work_observe() の責務を再定義するdiff_summary 自動生成方針を削除するrecent_records / active_issue / available_next を定義するwork_observe() の返値再設計
changed_files は残すrecent_records を追加するactive_issue を追加するrequired_action と available_next を分離するwork_diff の実装
work_history の最小実装
git log 代替として Brain-facing に公開するn / path 指定と record_id 返却に絞るgit_equivalents 機構の実装(実装済み)
work_vcs から参照できる形にするwork_vcs の実装(実装済み)
git 全般をブロックするgit_equivalents から誘導メッセージを生成するrecent_records のデフォルト件数recent_records に含めるフィールドの確定active_issue の取得元と、タイトル取得失敗時のフォールバックdefault_issue を返値に残すか、互換フィールドとしてのみ扱うか.claude/settings.json の hooks セクション): 設定済みsudo git ... / env git ... / GIT_PAGER=cat git log / pipe 後段の git を確実に捕捉することgit_equivalents を backend metadata から tool registration metadata に昇格するかwork_history を Phase 1 に含めるか Phase 2 に回すか: #69 の最小実装に含め、高度化は Phase 2 に回す