🔶 Docket:为 Agent 编写的代码生成逐提交证据记录
英文标题:🔶 Docket – Per-commit evidence records for agent-written code
Docket 是一款面向 Agent 编写代码的本地工具,用于将 Agent 的编辑过程、测试结果、覆盖率和人工接触记录与 Git 提交差异逐块关联。它把每个提交的证据记录保存在仓库的孤立 ref 中,并通过签名和摘要校验确保记录与提交内容绑定,无需账户或网络服务。Docket 支持 Claude Code、Codex CLI 和 opencode,并可通过 hooks 观察经由 shell、格式化器或生成器发生的文件修改。其核心价值在于让代码审查优先关注缺少归因或验证证据的 diff 区块,而不是把难以证实的推断伪装成可靠结论。
685.4 KB
124.3 KB
中文
面向 Agent 编写代码的逐提交证据记录。
编程 Agent 产出代码的速度,已经超过任何人能够验证它的速度。审查者收到的是一份完成后的 diff,却看不到实现过程:没有记录 Agent 最初尝试了什么、验证了什么,或哪些代码行从未被查看过。执行框架本已产生这些过程信息,却会在提交时将其丢弃。
Docket 会捕获这些信息,将其与 diff 对齐,并生成逐 hunk 的证据记录——让审查注意力落在缺少证据的地方。
$ 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
无需账户,无需网络。记录存放在仓库中,位于一个孤立 ref 上。
一个独立的静态二进制文件。无需 Go 工具链、无需运行时、无需编译。
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
或者,从发布页面下载对应平台的归档文件——支持 arm64 和 x86-64 的 macOS、Linux,以及两种架构的 Windows——解压后将 docket 放入 PATH。每个版本都会发布 SHA256SUMS;安装器会自动校验。
仍然为希望如此的用户保留从源码构建的方式:
go install github.com/Dillonsmart/docket/cmd/docket@latest
然后,在一个仓库中:
在插桩前先看看效果
它会在临时目录中创建一个一次性仓库,其中包含一段已记录的会话:一个 Agent 修复 session fixation 漏洞,先错了一次,看到测试失败后再正确修复——随后打印该提交的 docket。你的仓库不会被改动,目录会在退出时删除。
重新运行安装器即可。它会在原位置将二进制文件覆盖为最新版本:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
docket version 会显示当前安装的版本;发布页面会显示最新版本。若要固定版本或回退,请指定标签:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | DOCKET_VERSION=v0.0.2 sh
(该变量应设置在管道符 sh 一侧。若放在 curl 前面,它只会为下载过程设置,而不会传给脚本。)
从源码安装则使用 go install github.com/Dillonsmart/docket/cmd/docket@latest。在 CI 中,Action 默认的 version: latest 会在每次运行时使用最新版本;如果你希望自行决定何时升级,请将其固定到某个标签。
无需迁移任何内容。已存储的记录仍然可读——每条记录都带有其写入时对应的 schema 版本——本地除签名密钥外没有其他状态,而升级不会触碰该密钥。
只有当二进制文件安装到了新的位置时,才需要重新运行 docket init。hooks 会通过绝对路径调用它,若失败才回退到 PATH 中的 docket;因此原地升级无需任何操作。只有安装到不同目录并删除旧副本时,已记录的路径才会失效。
这会安装一个 prepare-commit-msg hook(用于写入 trailer——被审计的 Agent 不会自行写入审计记录)、一个 post-commit hook(用于存储记录)、refs/docket/* refspec、一个本地签名密钥,以及使 docket 能够观察通过 shell 进行编辑的 Claude Code hooks。
从此以后,每次提交都会在 Signed-off-by 和 Reviewed-by 所在的 trailer 区块中多出一行:
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 保持不变,提交主题行不会被修改,而摘要指向的是 refs/docket/records 上的一份已签名记录,不是某个 URL——因此永久历史中的任何内容都不依赖某项服务持续存在。
Amend 会重建记录并替换 trailer,因此一次提交永远不会指向它曾经拥有的旧内容。合并提交不会获得 trailer,不含任何可归因内容的提交也不会有。Rebase 和 cherry-pick 是棘手的情况:Git 不会为两者运行 prepare-commit-msg,因此 trailer 会被带到并非基于其构建的 diff 上——docket verify 会明确报告这一点,而不是信任该 trailer。
若要停止使用:删除 .git/hooks 中的两个 hook。已有 trailer 将作为无效文本保留在历史中。
docket show HEAD # the record for a commit, risk-ordered
docket explain src/auth.js:51 # why does this line exist? (via git blame)
docket review --format md # the pull request comment
docket verify HEAD # digest, signature and commit binding
docket push # send the records to the remote
docket doctor # what can docket see here, and what can't it
docket gate --commits 20 # measure attribution against real history
对于 Pull Request,GitHub Action会读取提交随附的记录,并发布一条评论:将没有任何证据能够担保的 hunk 排在前面,将证据充分的样板代码折叠在下方。它下载的是同一个二进制文件,因此 runner 上也无需安装任何内容。
重新理解他人的推理过程
回答_为什么这段代码会是这个样子_的字段包括 task、intent 和 attempt:
-
task —— 此次编辑所源自的请求,也就是人类实际提出的需求。
-
intent —— Agent 在进行编辑前一句话中所说的操作意图。推理会以 Agent 自己的表述落在这里。
-
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
jsonbnormalises key order, so the replayed response wasn't byte-identical to the original. For a stored response we only ever return verbatim,jsonis the right column type. attempt Now the schema. Replacing the defaultuserstable withplayersas the authenticatable model. [superseded]
docket explain 通过 git blame 查找对应提交,因此你可以从眼前的代码开始,而不必先知道应查看哪一笔提交。如果你更愿意以数据形式读取,docket show <sha> --all --json 会给出完整记录。
Docket 存储的是经过脱敏的短摘录,而不是完整对话:信息足以重建决策过程,却绝不保留完整转录,因为完整转录会携带密钥,并且会无限增长。
**归因。**Docket 将 Agent 转录解析为有序事件流,对每个文件重放所有已记录的编辑,并在后续编辑中持续传递来源向量——每一行对应一个来源。在提交时,它将已提交的文件与该重放结果对齐,并解析每个 hunk。
仅当某一行文本既能在对齐位置找到,_并且_也出现在被归功编辑的已记录输出中时,该行才会被归因。时间戳只用于排序事件;它们绝不能作为归因依据。
当某次编辑的已记录前镜像与重放结果不一致——例如 shell 命令、编辑器或某个人在中间修改了文件——系统会检测到这种不一致,将受影响的行标为 unknown,并从已记录的真实内容重新建立重放。带着不一致继续进行重建,正是工具会给出自信却错误答案的原因。一个自信却错误的归因引擎,比没有产品更糟糕,因此 unknown 始终是可用答案,并且总会附带原因。
**Agent。**Docket 可读取 Claude Code、Codex CLI 和 opencode。下游的所有环节——重放、归因、证据和记录——都与 Agent 无关,因此支持另一种 Agent 只需增加一个读取器,而无需重新设计:
| Agent | 其会话所在位置 | docket 从中获取的内容 |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl |
包含前后镜像的 Edit/Write 调用、shell 命令、提示词、Agent 自己的叙述 |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl |
apply_patch 调用、含退出码的 shell 命令、提示词、叙述 |
| opencode | ~/.local/share/opencode/opencode.db(需要 sqlite3) |
带 diff 的 write/edit 调用、含退出码的 shell 命令、提示词、叙述 |
| 其他任何工具 | — | 将其 hooks 接入 docket collect pre / docket collect post,即可直接观察编辑 |
Codex 和 opencode 传递的是 patch 而非完整文件,因此其编辑不带前镜像。Docket 从基础修订版本建立重放,并将 patch 应用到它实际所针对的内容上;如果无法匹配,系统会重新建立重放,并将无法解释的部分标记出来,而不是靠猜测定位 hunk。
**观察。**读取转录可以恢复 Edit 和 Write 调用。但如果 Agent 通过 shell 写文件——例如 heredoc、sed -i、生成器或格式化器——转录中就没有可恢复的信息。因此 docket 也会监视工作树本身:PreToolUse hook 对内容创建快照,PostToolUse hook 对其执行 diff,两份镜像都存储为 Git blob。这些编辑被标记为 observed 而非 transcript,因为它们由 docket 自行读取。
**证据。**测试运行、类型检查和静态分析会与其后发生的编辑关联——在代码写入_之前_运行的检查,不能作为该代码的证据。覆盖率报告(istanbul coverage-final.json、lcov)会逐行与 hunk 匹配;如果报告早于它声称覆盖的代码,则会被忽略。
**密度。**每个 hunk 有一个介于 0 和 1 之间的数值,完整公开,而非私下调优:这些行的覆盖率最高价值为 0.5,编辑后通过的检查价值为 0.3(若此次变更使其转绿,则为 0.35),类型检查和静态检查合计为 0.1,已记录的人类接触为 0.1。如果没有任何内容执行该代码,得分上限为 0.15;若无人能说明它是谁写的,上限为 0.5。本地声明的记录,其得分仅为 CI 证明记录应得分数的 0.9。
这些上限正是关键所在。这个数字会像覆盖率一样被转化为目标,而一种无需运行任何东西就能提高的指标,比没有指标更糟糕。
归因实际上表现如何
Docket 会衡量自身。docket gate 会针对真实提交重放真实会话,并报告它能解释和不能解释的内容:
| 会话 | Agent | 会话编辑过的文件中的 hunk | 新增行 | 内容已验证 |
|---|---|---|---|---|
| 一个 Laravel engine,Edit/Write 工具(4 次提交,98 次编辑) | Claude Code | 98.1% | 99.8% | 100% |
一个 Python 应用,apply_patch(4 次提交,256 次编辑) |
Codex CLI | 94.7% | 95.7% | 99.8% |
| 一个 FastAPI 项目,100 次编辑(未提交,针对工作树测量) | opencode | — | 92.9% | — |
| 一个主要通过 shell 构建的 PHP 框架(12 次提交,79 次编辑) | Claude Code | 59.2% | 82.1% | 100% |
这里有两点需要注意。每一项归因都根据被归功编辑自身的已记录输出进行了验证——docket 不会把某行归功给一项并未写出该行的编辑。最后一行则说明了为什么需要 collector:这些会话使用 heredoc 和 sed 写入文件,而任何转录都不会记录这些操作,并且这些工作发生在 docket 有机会观察之前。部署 docket init 后,这些编辑会被直接观察到。
在完整 diff 中,标题性比率会更低——40%、94%、37%——因为真实提交还包含 composer.lock、脚手架生成的模型和生成代码,这些内容从未被任何 Agent 编辑触及。Docket 会将这些内容报告为 unknown,并把附近运行过的命令列为_候选项_,绝不会将其当成归因。这才是诚实的答案,并且系统刻意不会将其平滑进数字中。
在你自己的历史上运行它:
- 不是编排、任务分配或 Agent 启动工具。
- 不是 Agent 间交接或供应商路由工具。
- 不是一个让你长期驻留的窗口。它标注的是你已经在使用的审查界面。
- 不是实时仪表盘。
- 不是一个在真正做任何事之前要求你注册账户的工具。
转录包含原始提示词、文件内容和终端输出。因此:
- 所有进入记录的内容都会经过严格的脱敏处…
English / 原文
A per-commit evidence record for agent-written code.
Coding agents produce code faster than anyone can verify it. The reviewer receives a finished diff with no implementation journey: no record of what the agent tried first, what it verified, or which lines nobody ever looked at. The harness already emits that journey and then throws it away at commit time.
Docket captures it, folds it against the diff, and produces a per-hunk evidence record — so review attention lands where there is no evidence.
$ 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
No account. No network. The records live in the repository, on an orphan ref.
A single static binary. No Go toolchain, no runtime, nothing to compile.
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
Or take the archive for your platform from the releases page — macOS and Linux on arm64 and x86-64, Windows on both — unpack it, and put docket on your PATH. Every release publishes SHA256SUMS; the installer checks them for you.
Building from source stays available for anyone who wants it:
go install github.com/Dillonsmart/docket/cmd/docket@latest
Then, in a repository:
See it before instrumenting anything
It builds a throwaway repository in a temp directory with a recorded session in it — an agent fixes a session fixation bug, gets it wrong once, watches a test fail, fixes it properly — and prints the docket for the commit. Your own repositories are not touched, and the directory is deleted on exit.
Re-run the installer. It overwrites the binary in place with the newest release:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | sh
docket version says what you have; the releases page says what is current. To pin, or to go back, name the tag:
curl -fsSL https://raw.githubusercontent.com/Dillonsmart/docket/main/install.sh | DOCKET_VERSION=v0.0.2 sh
(The variable goes on the sh side of the pipe. In front of curl it would be set for the download and not for the script.)
From source, it is go install github.com/Dillonsmart/docket/cmd/docket@latest. In CI, the action's default version: latest takes the newest release on every run; pin it to a tag if you would rather decide when that happens.
Nothing needs migrating. Records already stored stay readable — each one carries the schema version it was written against — and there is no local state beyond the signing key, which upgrades never touch.
You only need to re-run docket init if the binary lands somewhere new. The hooks call it by absolute path, falling back to whatever docket is on PATH, so an upgrade in place needs nothing; installing to a different directory and deleting the old copy is the case where the recorded path goes stale.
That installs a prepare-commit-msg hook (which writes the trailer — the audited agent never writes its own audit record), a post-commit hook (which stores the record), the refs/docket/* refspec, a local signing key, and the Claude Code hooks that let docket observe edits made through the shell.
From then on, every commit gets one extra line, in the trailer block where Signed-off-by and Reviewed-by live:
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
That is the whole footprint in your history. git log --oneline is unchanged, your subject line is untouched, and the digest names a signed record on refs/docket/records rather than a URL — so nothing in the permanent history depends on a service still existing.
Amending rebuilds the record and replaces the trailer, so a commit never points at the content it used to have. A merge gets no trailer, and neither does a commit with nothing attributable in it. Rebases and cherry-picks are the awkward case: git does not run prepare-commit-msg for either, so the trailer travels onto a diff it was not built from — docket verify reports that plainly rather than trusting the trailer.
To stop: delete the two hooks in .git/hooks. Existing trailers stay in the history as inert text.
docket show HEAD # the record for a commit, risk-ordered
docket explain src/auth.js:51 # why does this line exist? (via git blame)
docket review --format md # the pull request comment
docket verify HEAD # digest, signature and commit binding
docket push # send the records to the remote
docket doctor # what can docket see here, and what can't it
docket gate --commits 20 # measure attribution against real history
For pull requests, the GitHub Action reads the records the commits brought with them and posts one comment, reordering the diff so the hunks nothing can vouch for come first and the well-covered boilerplate is collapsed underneath. It downloads the same binary, so nothing needs installing on the runner either.
Reading someone's reasoning back
The fields that answer why does this code look like this are task, intent and attempt:
-
task — the request this edit descends from, i.e. what the human actually asked for.
-
intent — what the agent said it was doing in the sentence immediately before it made the edit. This is where the reasoning lands, in the agent's own words.
-
attempt — code written into this same region and then taken out again, with the check that failed in between when there was one. The abandoned approaches are the part that is otherwise lost within hours.
$ 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
jsonbnormalises key order, so the replayed response wasn't byte-identical to the original. For a stored response we only ever return verbatim,jsonis the right column type. attempt Now the schema. Replacing the defaultuserstable withplayersas the authenticatable model. [superseded]
docket explain finds the commit through git blame, so you can start from the code in front of you rather than having to know which commit to look in. docket show <sha> --all --json gives the whole record if you would rather read it as data.
Docket stores short redacted excerpts, not the conversation: enough to reconstruct the decision, never the whole transcript, because whole transcripts carry secrets and grow without bound.
Attribution. Docket parses the agent transcript into an ordered event stream, replays every recorded edit per file, and carries a provenance vector — one origin per line — forward through subsequent edits. At commit time it aligns the committed file against that replay and resolves each hunk.
A line is attributed only when its text is found at the aligned position and in the recorded output of the edit being credited. Timestamps only order events; they never justify an attribution.
When an edit's recorded pre-image disagrees with the replay — because a shell command, an editor or a person changed the file in between — the disagreement is detected, the affected lines become unknown, and the replay re-seeds from the recorded truth. Carrying a reconstruction past a disagreement is how tools produce confident, wrong answers. An attribution engine that is confidently wrong is worse than no product , so unknown is always an available answer, and it always comes with a reason.
Agents. Docket reads Claude Code, Codex CLI and opencode. Everything downstream — replay, attribution, evidence, the record — is agent-agnostic, so supporting another agent is a reader, not a redesign:
| Agent | Where its session lives | What docket gets from it |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl |
Edit/Write calls with before and after images, shell commands, prompts, the agent's own narration |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl |
apply_patch calls, shell commands with exit codes , prompts, narration |
| opencode | ~/.local/share/opencode/opencode.db (needs sqlite3) |
write/edit calls with diffs, shell commands with exit codes , prompts, narration |
| anything else | — | wire its hooks to docket collect pre / docket collect post and the edits are observed directly |
Codex and opencode send patches rather than whole files, so their edits carry no before-image. Docket seeds the replay from the base revision and applies the patch to the content it was actually written against; when that does not fit, it re-seeds and marks what it cannot explain rather than placing the hunk by guesswork.
Observation. Reading the transcript recovers Edit and Write calls. An agent that writes files through the shell — a heredoc, sed -i, a generator, a formatter — leaves nothing there to recover. So docket watches the working tree itself: a PreToolUse hook snapshots content, a PostToolUse hook diffs it, and both images are stored as git blobs. Those edits are marked observed rather than transcript, because docket read them itself.
Evidence. Test runs, type checks and static analysis are correlated with the edits they followed — a check that ran before the code was written is not evidence about it. Coverage reports (istanbul coverage-final.json, lcov) are matched line by line against the hunk, and ignored when the report is older than the code it would otherwise appear to cover.
Density. One number per hunk, between 0 and 1, published in full rather than tuned in private: coverage of these lines is worth at most 0.5, a check that passed after the edit 0.3 (0.35 if this change turned it green), type and static checks 0.1 together, recorded human contact 0.1. If nothing executed the code, the score is capped at 0.15. If nobody can say who wrote it, at 0.5. A locally-claimed record scores 0.9 of what a CI-attested one would.
Those caps are the point. This number will be turned into a target, exactly as coverage was, and a metric you can raise without running anything is worse than no metric.
How well does attribution actually work
Docket measures itself. docket gate replays real sessions against real commits and reports what it could and could not explain:
| Session | Agent | Hunks in files the session edited | Added lines | Content-verified |
|---|---|---|---|---|
| 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% |
Two things to take from this. Every attribution was verified against the crediting edit's own recorded output — docket did not credit a line to an edit that did not write it. And the last row is why the collector exists: those sessions wrote files with heredocs and sed, which no transcript records, and that work happened before docket could observe it. With docket init in place, those edits are observed directly.
Across whole diffs the headline rate is lower — 40%, 94%, 37% — because real commits also contain composer.lock, scaffolded models and generated code that no agent edit ever touched. Docket reports those as unknown with the commands that ran nearby listed as candidates , never as attributions. That is the honest answer, and it is deliberately not smoothed into the number.
Run it on your own history:
- Not orchestration, task assignment or agent spawning.
- Not agent-to-agent handoff or provider routing.
- Not a window you live in. It annotates the review surface you already use.
- Not a live dashboard.
- Not something that asks for an account before it does anything.
Traces contain raw prompts, file contents and terminal output. So:
- Everything that reaches a record goes through an aggressive redaction pass: known credential shapes, private keys, JWTs, authorization headers, connection-string credentials,
KEY=/SECRET=/PASSWORD=assignments — and, failing closed, any long token whose character distribution looks random. - A record stores short redacted excerpts, never whole files or whole prompts.
- Nothing is sent anywhere. The records live in your repository, and
docket pushsends them to the git host you already trust. - The signing key lives in
.git/docket/and never leaves the machine.
| Tier | Meaning |
|---|---|
local_claimed |
Built on a developer machine, signed with a key that machine holds. It is a claim. |
ci_attested |
Built by a runner, signed with a key the developer does not hold. Set DOCKET_SIGNING_KEY in CI. |
The distinction is in the schema from day one and is shown everywhere a record is rendered.
The record format is specified separately as the Commit Evidence Record, with a JSON Schema. Docket is its reference implementation. The specification is Apache 2.0 and deliberately boring: it is meant to be implemented by other tools, including ones that compete with this one.
Built and working: attribution, the shell-edit collector, redaction, signed records on an orphan ref, coverage and test correlation, the terminal viewer, explain, the pull request comment, the GitHub Action, and the self-measurement gate.
Not built yet: readers for Gemini CLI and anything ACP-native, coverage correlation beyond istanbul/lcov, GitLab, cross-repository aggregation, policy gates on paths, and the hosted team tier.
