Docket – エージェントによって書かれたコードのコミットごとの証拠記録
Docket – Per-commit evidence records for agent-written code (github.com)
要約
Docketは、AIコーディングエージェントが生成したコードのコミットごとに、その実装の過程を記録するツールです。エージェントが試したこと、検証したこと、レビューされなかった行などを追跡し、コードの変更点(hunk)ごとに証拠記録を作成します。これにより、コードレビューの際に、どこに注意を払うべきか(証拠が少ない箇所)を特定しやすくなります。インストールは簡単で、Gitリポジトリにフックを仕掛けることで機能します。
全文翻訳
[画像: Docket](https://github.com/Dillonsmart/docket/blob/main/Docket.png)
エージェントが書いたコードの、コミットごとの証拠記録。
なぜ
リポジトリ内のコードの多くが、しばしば自動モードで実行されるコーディングエージェントによって書かれるようになっています。エージェントはコードを読み、アプローチを選択し、編集し、テストを実行し、失敗した箇所を修正して、完成した差分を返します。その過程で、開発者が決して目にすることのない決定を下し、チェックを実行しました。
開発者は依然としてそのコードの所有者です。レビューで説明し、壊れたときに修正し、それを信頼するかどうかを決定する必要があります。エージェントが何を試したのか、何を検証したのか、どの行が誰にも見られなかったのかを知らなければ、それは困難です。
エージェントハーネスはすでにこれらすべてを記録しています。コミット時に破棄されます。Docketはそれを保持します。セッションを読み取り、差分と照合し、各コミットに対して署名された証拠記録を保存します。これにより、任意のハングや任意の行について、誰がそれを書いたのか、なぜ書いたのか、その前に何を試したのか、そして何をテストしたのかを見ることができます。
$ docket show HEAD
docket 7a9da95a2a748c2fb99706f48f2ddf64760ffab2
record sha256:046c5278e93cb516e3ba72400fd1a69648c85b93833d31d57648e9ed8e535ebf
trust local_claimed (signed on local)
verified digest and signature check out (ed25519:f889a26cbc34c95e)
1 hunks in 1 files, 4 added lines
100% of added lines attributed to a recorded edit
mean evidence density 0.77
src/auth.js:3-6 (4 lines) covered density 0.77
origin claude-code/main via Edit (reported by the harness)
claude-opus-5
task Fix session fixation on login
intent The raw UUID is reused across logins, so minting a prefixed id instead.
attempt Stamping a creation time on the session so expiry can be checked. [superseded]
vitest failed after this change: npx vitest run
evidence coverage: 4 of 4 lines executed (coverage/coverage-final.json)
evidence test_execution: pass (npx vitest run --coverage) — failed before this change
human no recorded human contact with these lines — the edit was written without a prompt (claude-code acceptEdits)
アカウントもネットワークアクセスもありません。記録は、孤立した参照(refs/docket/records)上でリポジトリ内に存在します。
インストール
Docketは単一の静的バイナリです。macOSまたはLinuxの場合:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
Windowsの場合、またはshにパイプしたくない場合は、リリースページ(macOS、Linux、Windows、arm64およびx86-64)からお使いのプラットフォーム用のアーカイブをダウンロードし、解凍してdocketをPATHに配置してください。各リリースはSHA256SUMSを発行し、インストーラーはダウンロードをそれと照合します。
ソースからビルドするには:
go install github.com/Dillonsmart/docket/cmd/docket@latest
まず試してみる
scripts/demo.sh
これは、記録されたエージェントセッションを含む一時ディレクトリ内に使い捨てリポジトリを作成します。エージェントはセッション固定バグを修正し、一度間違え、テストの失敗を見て、正しく修正し、さらにシェル経由で2番目のファイルを編集しますが、これはトランスクリプトには記録されません。その後、コミットのDocketを印刷するので、両方の種類のハングを見ることができます。あなた自身のレポジトリは変更されず、ディレクトリは終了時に削除されます。PATHにdocketが必要か、DOCKET=/path/to/docketが必要です。
または、このリポジトリ自体の記録を読む
Docketは自身の開発を記録しています。それをクローンし、記録を取得し、ツールからツールの履歴を読みます:
git clone https://github.com/Dillonsmart/docket && cd docket
docket fetch # 記録はrefs/docket/recordsに存在しますが、gitはデフォルトではフェッチしません
docket show HEAD # 最後のコミット、最もリスクの高いハングから
docket show --all HEAD # 背後に証拠があるハングを含む
# エージェントがシェル経由で行ったコミット: docketはファイルを監視し、テストは同じ呼び出しで実行されました
docket show --all $(git log --format=%h --grep='Join an observed edit' -1)
# この関数はなぜ存在するのですか? git blameはコミットを見つけ、記録はハングを説明します
docket explain internal/build/build.go:$(grep -n 'func link(' internal/build/build.go | cut -d: -f1)
docket review --range v0.0.5..v0.0.6 --format md # リリースを1つの変更としてレビュー、最も検証されていないハングから
docket verify HEAD # 記録は無傷で署名されており、このコミットを説明しています
docket doctor # このマシンでdocketが見えるものと見えないもの
記録は作成者のマシンで作成されたため、trust local_claimedと表示されます。docket gateと--rebuildはセッショントランスクリプトを必要としますが、それらはそのマシンに残ります。それ以外のすべてはクローンから機能します。記録はコミット時に一度だけ構築され、その時点でdocketができたことすべてが使用されます。そのため、古いコミットは新しいものよりも薄い記録を持ち、それはバグではなく履歴です。
リポジトリの設定
docket init
これにより以下がインストールされます:
prepare-commit-msggitフック:記録を構築し、そのダイジェストをコミットメッセージのトレーラーとして書き込みます。post-commitgitフック:記録をrefs/docket/recordsに保存します。originに対するrefs/docket/*のフェッチリファスペック。これによりgit fetchで記録がダウンロードされます(docket pushでアップロードされます。リモートがまだない場合は、リモートができた後に再度docket initを実行してください)。.git/docket/内のローカル署名キー。- Claude Codeの
PreToolUse/PostToolUseフック(.claude/settings.local.json内)。これにより、docketはエージェントがシェル経由で行う編集を見ることができます。これはコミットされたsettings.jsonではなく、マシンごとのファイルです。フックはマシン上のバイナリへのパスを指定します。
フックはコミットをブロックしません。Docketが見つからないか、記録が構築できない場合、コミットは続行され、エラーは.git/docket/docket.logに書き込まれます。
それ以降、各コミットにはSigned-off-byやReviewed-byと並んで、1つの追加トレーラー行が付きます:
Add the session helper
The API needs a stable id per session, and the obvious place is here
rather than in the middleware.
Reviewed-by: Someone Else <someone@example.com>
Docket: sha256:db77fdb4b0c1fc6cc7644d3fa2204267e25799d74a6f8f665aff3d32b12c39af
これは履歴への唯一の変更です。件名行とgit log --onelineは変更されず、トレーラーはURLではなくリポジトリ内の記録を参照するため、サービスが継続することに依存しません。
知っておくべきいくつかのケースがあります:
- Amend: 記録を再構築し、トレーラーを置き換えます。そのため、コミットはもはや存在しないコンテンツの記録を参照することはありません。
- Merges: トレーラーは付きません。また、帰属させるものが何もないコミットにも付きません。
- Rebase and cherry-pick:
prepare-commit-msgは実行されないため、トレーラーはビルドされなかった差分にコピーされます。docket verifyはこれを報告し、トレーラーを信頼しません。
Docketの使用を停止するには、.git/hooksから2つのフックを削除してください。既存のトレーラーはプレーンテキストとして履歴に残ります。
アップグレード
インストーラーを再実行すると、バイナリがその場で置き換えられます:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
docket versionは現在インストールされているバージョンを表示します。バージョンを固定したり、ロールバックしたりするには、パイプのsh側でDOCKET_VERSIONを設定します:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | DOCKET_VERSION=v0.0.7 sh
ソースからは、go install github.com/Dillonsmart/docket/cmd/docket@latestを再度実行します。CIでは、アクションのデフォルトversion: latestは実行ごとに最新リリースを取得します。それをタグにピン留めすると、いつそれが起こるかを制御できます。
移行が必要なものはありません。保存された記録は、書き込まれたときのスキーマバージョンを保持し、読み取り可能であり、ローカル状態は署名キーのみで、アップグレードでは変更されません。フックは絶対パスでバイナリを呼び出し、PATH上のdocketにフォールバックするため、古いバイナリを削除して新しい場所にインストールした場合にのみdocket initを再実行する必要があります。
使用方法
docket show HEAD # コミットの記録、最もリスクの高いハングから
docket build --staged # コミットせずにステージングされた変更の記録をビルドする
docket explain src/auth.js:51 # この行はなぜ存在するのか? (git blame経由で発見)
docket review --format md # プルリクエストコメント
docket verify HEAD # ダイジェスト、署名、コミットバインディングを確認する
docket push # リモートに記録を送信する
docket fetch # リモートから記録を取得する
docket doctor # このリポジトリでdocketが見えるものと見えないもの
docket gate --commits 20 # 自身の履歴に対して帰属を測定する
プルリクエストの場合、GitHub Actionはコミットに含まれる記録を読み取り、1つのコメントを投稿します。証拠のないハングを最初に配置し、十分にカバーされたものを下に折りたたみます。ランナーに何もインストールする必要はありません。
推論の読み戻し
3つのフィールドが「なぜこのコードはこのようになっているのか」に答えます:
- task: 人間の言葉で、この編集が来たリクエスト。
- intent: 編集直前のメッセージで、エージェントが何をしようとしていたか。これはエージェント自身の言葉での推論です。
- attempt: この領域に書き込まれ、その後削除されたコード。その間に失敗したチェックがあれば、それも含まれます。これらの放棄されたアプローチは、通常数時間で失われる部分です。
$ docket explain database/migrations/0001_01_01_000000_create_players_table.php:54
database/migrations/…:54 was last written by be66f35c5977
origin claude-code/main via Edit
task Look at the engine plan for this project, challenge any assumptions then start implementing
intent Postgres `jsonb` normalises key order, so the replayed response wasn't byte-identical
to the original. For a stored response we only ever return verbatim, `json` is the
right column type.
attempt Now the schema. Replacing the default `users` table with `players` as the
authenticatable model. [superseded]
docket explainはgit blameを使用してコミットを見つけるため、探しているコミットを知る必要なく、目の前の行から始めることができます。docket show <sha> --all --jsonは、レコード全体をJSON形式で出力します。
Docketは、会話全体ではなく、短く編集された抜粋を保存します。それは決定を再構築するのに十分ですが、完全なトランスクリプトには機密情報が含まれ、際限なく増加します。
仕組み
帰属。 Docketはエージェントトランスクリプトをイベントの順序付けられたストリームに解析し、ファイルごとに記録されたすべての編集を再生し、後続の編集を通じて各行の発生元を追跡します。コミット時に、コミットされたファイルをその再生と整列させ、各ハングを解決します。
行は、そのテキストが整列された位置でかつその編集の記録された出力で見つかった場合にのみ、編集に帰属されます。タイムスタンプはイベントの順序付けに使用され、帰属を正当化するためには使用されません。
編集の記録されたビフォーイメージが再生と一致しない場合(シェルコマンド、エディタ、または人間がその間にファイルを変更したため)、Docketは影響を受ける行をunknownとマークし、記録されたコンテンツから再生を再開します。推測を前に進めることはありません。unknownは常に利用可能な回答であり、常に理由が付随します。なぜなら、自信を持って間違った帰属は、まったくないよりも悪いからです。
エージェント。 DocketはClaude Code、Codex CLI、opencodeからのセッションを読み取ります。リーダー以降のすべてはエージェントに依存しないため、エージェントを追加することはリーダーを追加することになります:
| エージェント | セッションの場所 | Docketが取得するもの |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl (または$CLAUDE_CONFIG_DIR) | Edit/Write呼び出し(ビフォー/アフター画像、シェルコマンド、プロンプト、エージェントのナレーションを含む) |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl | apply_patch呼び出し、シェルコマンド(終了コード付き)、プロンプト、ナレーション |
| opencode | ~/.local/share/opencode/opencode.db (sqlite3のインストールが必要) | write/edit呼び出し(差分付き)、シェルコマンド(終了コード付き)、プロンプト、ナレーション |
| その他 | — | docket collect preとdocket collect postにフックをポイントさせると、編集が直接観察されます |
Codexとopencodeはファイル全体ではなくパッチを送信するため、それらの編集にはビフォーイメージがありません。Docketはベースリビジョンから再生をシードし、各パッチをそれが書き込まれたコンテンツに適用します。パッチが適合しない場合、再シードし、ハングがどこにあるかを推測するのではなく、説明できないものをマークします。
観察。トランスクリプトを読むことで、EditとWriteの呼び出しが復元されます。シェル経由でファイルを書き込むエージェント(heredoc、sed -i、ジェネレータ、フォーマッタ)は、トランスクリプトに復元できるものを何も残しません。そのため、Docketはワーキングツリーも監視します。PreToolUseフックはコンテンツのスナップショットを作成し、PostToolUseフックはそれを差分化します。両方の画像はgit blobとして保存されます。これらの編集は、transcriptではなくobservedとしてマークされます。なぜなら、Docketはそれらを自身で見たからです。
証拠。テスト実行、型チェック、静的解析は、それらが続いた編集と照合されます。コードが書かれる前に実行されたチェックは、それに関する証拠ではありません。カバレッジレポート(istanbul coverage-final.jsonおよびlcov)は、各ハングと行ごとに照合され、レポートがコードよりも古い場合は無視されます。
人間との接触。ターミナルには開封確認がないため、Docketはハーネスが記録したものしか主張しません。editedは、ハーネスがその下でファイルが変更されたのを目撃し、これらの行が異なるもののうちの1つであることを意味します(記録されたビフォーイメージからの行単位の精度)。approvedは、これらの行を書いた編集が許可プロンプトを経由したことを意味します(Claude Codeのdefaultモード、Codexのuntrustedポリシー、または読み取り専用サンドボックス)。これは強制された設定から推測されるものであり、観察されたものではありません。また、ツールごとの許可ルールは、モードが本来表示するはずのプロンプトを抑制できるため、すべての記録は主張の根拠(contact_basis)を示し、読者はそれを割引くことができます。それ以外はすべてnoneであり、根拠はハーネスが尋ねずに書いたか、トランスクリプトが単に何も言わないかを示します。opencodeは呼び出しごとの権限に関する何も記録しないため、その編集は常に後者です。
密度。各ハングには0から1の間のスコアが付けられます。計算式は仕様で公開されています。ハングの行のカバレッジは最大0.5、編集後にパスしたチェックは0.3(この変更で緑になった場合は0.35)、型チェックと静的チェックは合わせて最大0.1、記録された人間との接触は最大0.1です。その後、上限があります。何もコードを実行しなかった場合、スコアは0.15に上限設定されます。誰が書いたか誰も言えない場合、スコアは0.5に上限設定されます。ローカルで主張された記録は、CIで証明されたものより0.9のスコアになります。
上限が重要です。この数値は、カバレッジと同様のターゲットとして使用されることになり、何も実行せずに上げられるスコアは無価値になるでしょう。
帰属はどの程度機能するか
docket gateは実際のセッションを実際のコミットに対して再生し、説明できたこととできなかったことを報告します。これまでの測定結果:
| セッション | エージェント | セッションが編集したファイル内のハング | 追加行 | コンテンツ検証済み |
|---|---|---|---|---|
| A Laravel engine, Edit/Write tools (4 commits, 98 edits) | Claude Code | 98.1% | 99.8% | 100% |
A Python app, apply_patch (4 commits, 256 edits) | Codex CLI | 94.7% | 95.7% | 99.8% |
| A FastAPI project, 100 edits (uncommitted, measured against the working tree) | opencode | — | 92.9% | — |
| A PHP framework built largely through the shell (12 commits, 79 edits) | Claude Code | 59.2% | 82.1% | 100% |
ここからわかることは2つあります。すべての帰属は、クレジットされた編集自体の記録された出力と照合されたため、Docketは行をそれを書かなかった編集に帰属させることはありませんでした。そして、最後の行はシェル編集フックが存在する理由です。これらのセッションでは、Docketがそれらを観察できるようになる前に、heredocやsedを使用してファイルを書き込んでいました。docket initが設定されていれば、これらの編集はキャプチャされます。
差分全体で見ると、レートは低くなります(コミットされた3つのセッションでそれぞれ40%、94%、37%)。なぜなら、実際のコミットにはcomposer.lock、スキャフォールドされたモデル、エージェント編集が触れなかった生成されたコードも含まれるからです。Docketはそれらをunknownとして報告し、近くで実行されたコマンドを候補としてリストします。それらは帰属されたとはカウントされません。
あなた自身の履歴で実行してください:
docket gate --commits 20
これは何かではない
- オーケストレーション、タスク割り当て、エージェントのスポーニングではありません。
- エージェント間のハンドオフやプロバイダーのルーティングではありません。
- あなたが生活するUIではありません。既存のレビューサーフェスを注釈付けします。
- ライブダッシュボードではありません。
- アカウントを要求するものではありません。
プライバシー
トランスクリプトには生のプロンプト、ファイルの内容、ターミナル出力が含まれるため:
- レコードに到達するすべては、機密鍵ブロック、既知の認証情報形式(AWS、GitHub、GitLab、Slack、Stripe、Google、OpenAI、Anthropic、npm)、JWT、承認ヘッダー、URL内の認証情報、
KEY=/SECRET=/PASSWORD=スタイルの割り当て、およびランダムに見える文字を持つ長いトークンなど、すべてが編集パスを通ります。 - レコードは、ファイル全体やプロンプト全体ではなく、短く編集された抜粋を保存します。
- 何もどこにも送信されません。記録はあなたのリポジトリに存在し、
docket pushはそれらをあなたがすでに使用しているgitホストに送信します。 - 署名キーは
.git/docket/にあり、マシンから離れることはありません。
信頼
| ティア | 意味 |
|---|---|
local_claimed | 開発者のマシンで構築され、そのマシンが保持するキーで署名されています。これは主張です。 |
ci_attested | CIランナーによって構築され、開発者が保持しないキーで署名されています。CIでDOCKET_SIGNING_KEYを設定します。 |
ティアはスキーマの一部であり、レコードが表示される場所ならどこでも表示されます。
フォーマット
レコードフォーマットは、コミット証拠レコードとして別途指定されており、JSONスキーマもあります。Docketは参照実装です。仕様はApache 2.0であり、他のツールによって実装されるように書かれています。
ステータス
動作中:帰属、シェル編集フック、編集、孤立参照での署名付き記録、カバレッジとテストの相関、ターミナルビューア、explain、プルリクエストコメント、GitHub Action、およびgate。
まだ構築されていません:Gemini CLIおよびACPネイティブのリーダー、istanbulおよびlcov以外のカバレッジフォーマット、GitLab、リポジトリ間集計、パスに対するポリシーゲート、ホストされたチームティア。