Skip to content

Distilled diffs ​

Before submitting its PR description the coding agent gets a distilled summary of the change, not the raw diff: per-file +adds/-dels, totals, and a "risky files" section that flags migrations/schema, auth/permission-sensitive files, whole-file deletions, lockfiles, and very large files. The digest shapes the submitted description — which files the Tour must walk, what belongs in risks (pr-description.md) — so the reviewer meets the shape and the danger zones through the rendered body. The review agent uses the same digest to orient before reading.

The review agent reads the code; it does not run the project's tests or build. CI runs them as the repo's verify gate and reports on the PR, so a review that runs them too duplicates that signal and slows the review. (This supersedes the earlier "validated review" behavior, which had the review agent run the tests and build in its warm worktree and report pass/fail.)

The parsing/rendering core is distillDiffStats, a pure function over git's per-file statistics (git diff --numstat and git diff --name-status for one range; no I/O, no process, no platform SDK), so it is provider- and channel-agnostic and unit-testable in isolation. The diff_digest tool is the only thing that touches the world, and only through the Executor seam — never a direct shell-out — honoring the "tools never touch the host directly" invariant.

Why statistics and not the unified diff: every Executor caps a command's output (truncate, 120k characters), and the unified diff of a mid-sized PR is larger than that. The digest used to be parsed from the capped text, so it counted the first files in git diff's alphabetical order and nothing after them — a 41-file, +2459/−579 PR was digested as 13 files, +144/−53, and the review approved on it. The stat listings cost one line per file, so the digest covers every file however large the change, and a listing that is itself cut is reported as incomplete rather than rendered as a smaller change. The digest states its own totals, and the review post-step holds them against the PR's size from GitHub (agent-review.md item 15).

  • Code: src/core/diffDigest.ts (the pure distillDiffStats, the DigestReport shape and its ledger parser); src/tools/diffDigest.ts (the diff_digest RunnableTool; reuses src/execution/shellQuote.ts to quote the base ref) + src/tools/toolsets.ts (the full/readonly toolset wiring); src/agents/registry.ts (CODING_SYSTEM_RESIDENT has the digest inform the submitted description; REVIEW_SYSTEM / REVIEW_SYSTEM_RESIDENT orient with it, read the whole change, and forbid running tests/build).
  • Tests: src/core/diffDigest.test.ts, src/tools/diffDigest.test.ts, src/agents/registry.test.ts.

Behavior ​

  1. distillDiffStats is pure and provider-agnostic: git diff --numstat and git diff --name-status output for one range in, a compact digest out — totals line, per-file +adds/-dels (largest churn first), a risky-files section — plus the totals as data ({ files, additions, deletions }). No Executor, no filesystem, no network. Empty/whitespace input returns a clear "no changes" message with zero totals.

  2. Per-file/total counts come from the listings: --numstat gives each file's added and removed lines (-\t- for a binary file, counted as zero); --name-status gives its status (added / deleted / renamed / modified) and, for a rename, both paths plainly (R<score>\told\tnew) — git emits both listings over the same diff queue in the same order, so they are zipped by position. A renamed file is shown at its new path, never in git's compact {old => new} form. A listing line that does not align keeps its numstat path and counts as modified rather than being dropped.

  3. Risky-file heuristics (biased to over-flag — a false positive costs one glance, a missed migration costs more): path matches for migrations/schema (migrations/, *.sql, *.prisma, schema), auth/permission (auth, permission, credential, secret, password, .env, login, session, oauth, rbac, acl), infra/deploy/CI config (.github/workflows/, *.tf, terraform/, Dockerfile, deploy/, wrangler.*); whole-file deletions; lockfiles (exact basename: package-lock.json, yarn.lock, pnpm-lock.yaml, go.sum, Cargo.lock, …); and single-file churn ≥ 300 lines. Ordinary source files are not flagged. Risk is scored against BOTH the old and new paths, so a risky file renamed to an innocuous name (src/auth/x.ts → src/misc/y.ts, .env → config.json) is still flagged. Non-ASCII paths (git core.quotepath octal quoting) are decoded to the real filename.

  4. diff_digest tool bridges the Executor: runs git diff --numstat <base>...HEAD and git diff --name-status <base>...HEAD — the merge-base range, never HEAD~n — in one ctx.executor.exec and renders the listings with distillDiffStats. base is optional and defaults to the repo's default branch (origin/HEAD, falling back to origin/main). A caller-supplied base is shellQuoted into one inert token (no shell injection) behind --end-of-options (no git option injection; a base starting with - is refused before any command runs). A git failure (fatal:/exit/…) is surfaced as an error, never rendered as a misleading empty diff. A shallow clone is deepened first: when the listing fails and git rev-parse --is-shallow-repository answers true (the cold sandbox's own git clone --depth), the tool runs git fetch --unshallow origin for every branch once and retries; a full clone is never fetched (the resident's read-only tree has an origin it cannot fetch from by design, and its failure stays legible). A cut listing is never a smaller change: output carrying the Executor's ...[truncated N chars] marker makes the tool answer that it cannot state totals, and report the digest as incomplete.

  5. Enablement: diff_digest is in the full (coding) and readonly (review) toolsets; web and none are unchanged. ≥2-implementations and channel-agnostic invariants hold — distillDiffStats is our own implementation and does not depend on meat.dev (absent from the node-only resident image).

  6. The digest informs the submitted description: the resident coding prompt directs the agent to call diff_digest after pushing and before submit_pr_description, using the distilled digest — not the raw diff — to shape the description's content: which files the Tour must walk, what belongs in risks (pr-description.md item 5). The digest is never pasted into a PR body — the agent never authors body markdown and never opens the PR.

  7. Review reads, CI tests: the resident review prompt directs the agent, in its gather phase, to use diff_digest to orient before reading. Both review prompts (sandbox and resident) forbid running the project's tests or build — CI's verify gate owns that and reports on the PR — and neither asks the agent to report commands run or pass/fail. The agent stays read-only (no modifying tracked code, commits, or pushes) and keeps the existing "gather once, analyze once" discipline.

  8. The digest states its totals, and the run holds them: every diff_digest call reports a DigestReport through ToolContext.onDigest — { complete: true, base, totals } or { complete: false, base, reason } — a bound review also records the immutable merge-base/head range and canonical PR merge base. Incremental reports cannot replace canonical coverage for that same target; a changed head or base invalidates that choice. The dispatcher keeps the selected report on the run's ledger row beside the verdict (a resumed run re-validates the range; legacy, malformed or mismatched saved coverage requires a fresh digest). Retaining canonical coverage over an incremental report emits review_digest_retained with both immutable ranges; optional log failure cannot change selection. Bound-review range refusals also emit review_digest_refused with the controller target and a closed reason, never the caller's base expression. The review post-step compares those totals with the PR's size from GitHub and refuses to post a verdict whose digest covered less (agent-review.md item 15). All three review prompts share REVIEW_WHOLE_CHANGE: compare the digest totals with the PR size and read the full diff with the pinned source reader. If the request requires shell collection, save the read-only diff once in run scratch and page it with read. Truncated check receipts and unfinished pages are partial evidence; small shell slices are not the recovery method. Missing source keeps the review incomplete.

A complete saved digest without a range can be rebuilt before publication. The original saved repository, PR, base ref and head must match the review target and verdict. A fresh checkout HEAD read must confirm that head. The controller runs diff_digest with the saved base in that checkout and retains the result only when its immutable range is canonical and every total equals the saved total. Missing or changed pins, an incremental base, changed totals, a failed or cut listing, incomplete coverage and malformed saved ranges retain the publication refusal. This reconstruction runs no model turn or recorded check and does not repeat a publication.

Validation criteria ​

CriterionEvidence
distillDiffStats computes correct per-file and total add/delete counts, orders by churn, and returns the totals as data[unit] src/core/diffDigest.test.ts::distillDiffStats::computes correct per-file and total add/delete counts, ::orders files by churn, largest first, ::uses singular wording for a single-file diff
File status from the name-status listing; a rename shown at its new path, never {old => new}; binaries counted as zero[unit] ::labels file status (added / deleted / renamed) from the name-status listing, ::detects binary files (numstat \-\t-`) without counting content lines`
Risky flags: migration, auth, whole-file deletion, lockfile, large file; ordinary files not flagged[unit] ::flags risky files: migration, auth, whole-file deletion, lockfile, ::flags a very large file by total churn, ::does not flag ordinary source files as risky
Risk scored on BOTH paths: a risky file renamed to a bland name is still flagged[unit] ::flags a risky file renamed to a bland name (old-side risk), ::flags a secrets file relocated to a non-secret name (.env → config.json)
Infra/deploy/CI config flagged; git-quoted non-ASCII paths decoded[unit] ::flags infra/deploy/CI config files, ::decodes git-quoted non-ASCII filenames
Empty listings → the no-changes message with zero totals; misaligned listings never throw or drop files[unit] ::returns an empty-diff message with zero totals for empty or whitespace input, ::tolerates listings that do not align (a stray line) without throwing or dropping files
A legacy digest is rebuilt only for the original review target, head and canonical base; invalid or unavailable coverage cannot publish[unit] src/core/dispatch/runLoop.test.ts::a resume with the answer in hand (the \finish` plan)::rebuilds legacy digest coverage before publication only when bound: …`
A DigestReport read back from the ledger is re-validated: both shapes accepted, anything malformed refused[unit] src/core/diffDigest.test.ts::parseDigestReport (a ledger row read back on resume)::accepts both shapes and refuses anything malformed
diff_digest runs the merge-base range as --numstat + --name-status (never the unified diff, never HEAD~n), distills the listing, reports totals through onDigest; defaults to origin/HEAD[unit] src/tools/diffDigest.test.ts::diff_digest tool::distills the file listing the executor returns and reports its totals through onDigest, ::runs the merge-base range as stats — \--numstat` and `--name-status` over `...HEAD` — never the unified diff, ::defaults to the repo's default branch (origin/HEAD) when no base is given`
diff_digest surfaces git failures (no digest reported); base ref is neither shell- nor git-option-injectable[unit] ::diff_digest tool::surfaces a git failure instead of reporting an empty diff, and reports no digest, ::does not shell-inject through the base ref, ::rejects a base ref starting with '-' (git option injection) without running git, ::passes --end-of-options so a ref is never parsed as a git option
A shallow clone is deepened once, then retried; a full clone is never fetched[unit] ::diff_digest tool::a shallow clone with no merge base is deepened once (every branch), then the listing is retried, ::a full clone is never fetched: the failure is reported as is
A listing cut by the output cap is reported incomplete, never as a smaller change — wherever the cut lands, with no shallow probe; a path carrying the marker text never corrupts the split[unit] ::diff_digest tool::a listing cut by the executor's output cap is reported as incomplete — never rendered as a smaller change, ::a listing cut BEFORE the marker is still incomplete — never 'could not compute', and no shallow probe or fetch, ::a path that contains the marker text does not corrupt the split — the marker is a whole line, never a substring
On a real multi-commit branch through the real LocalExecutor, with a unified diff larger than the output cap, the digest lists every commit's files and states exact totals[unit] src/tools/diffDigest.test.ts::diff_digest tool on a real multi-commit branch (LocalExecutor)::covers every commit's files and states exact totals even when the unified diff exceeds the output cap
Enablement: diff_digest in full + readonly, not web/none[unit] src/tools/diffDigest.test.ts::diff_digest toolset wiring::is in the coding (full) and review (readonly) toolsets, not web/none
The digest resolves refs, lists files and deepens history in the dispatcher's acknowledged checkout even when the executor starts elsewhere; contexts without a binding retain the executor cwd[unit] src/tools/diffDigest.test.ts::diff_digest tool on a real multi-commit branch (LocalExecutor)::uses the bound checkout when the executor starts outside the repository, ::deepens the bound shallow checkout when the executor starts outside it, src/core/dispatch/runLoop.test.ts::runLoop checkout-bound tools::computes a digest in the acknowledged cold checkout
Coding resident prompt has the digest inform the submitted description (shapes the object's content; never pasted into a body)[unit] src/agents/registry.test.ts::distilled-diffs prompt behavior (resident variants)::coding resident: calls diff_digest to inform the submitted description …
Both review prompts forbid running tests/build and name CI as the owner; no run-tests instruction, no npm test, no pass/fail report ask; read-only kept[unit] ::review prompts read the code and never run the project's tests or build — CI does (item 7)
Review resident prompt orients with the digest and keeps gather-once[unit] ::review resident: orients with the digest, ::review resident keeps the gather-once discipline
Complete pinned source pages reconstruct the full diff, and partial digest coverage still refuses publication. Model source-reading choices require the live evaluation below.[unit] src/tools/reviewRead.test.ts::review_read::reconstructs a private full diff and oversized source line through bounded UTF8 byte pages; src/core/digestCoverage.test.ts::checkDigestCoverage::refuses the verdict when the digest covered fewer files than the PR, naming both sides
Live: after deploy, the first review of a multi-commit PR states a digest whose file count equals the PR's changed_files on GitHub, and the review posts[agent] human-gated: send agent:review <PR URL> for an open PR of several commits and more than ~120k characters of diff; on the run page the diff_digest result's first line reads N files changed with N equal to the PR's file count on GitHub (and +/− equal to its additions/deletions), and the verdict posts to the PR — no ℹ️ Review not posted … digest covered note in the thread.
Live: the digest actually shapes the coding PR's description; a review run makes no test/build call[agent] Send agent:coding … for a change touching several files and confirm the run page shows a diff_digest call before submit_pr_description and the bot-opened PR body's Tour steps cover the digest's top-churn files (flagged risky files surfacing under Risks); send agent:review <PR> and confirm the run page's bash calls contain no npm test / npm run build (no tests/build tag on any call) and the reply carries findings without a "commands run" section. Requires the resident deployment.

Proofs for canonical range retention: src/tools/diffDigest.test.ts::diff_digest tool on a real multi-commit branch (LocalExecutor)::binds full and incremental digests to their immutable review ranges; src/core/dispatch/runLoop.test.ts::runLoop checkout-bound tools::retains full-range coverage after an incremental digest; src/core/diffDigest.test.ts::canonical digest retention::keeps canonical coverage through restart and both inspection orders; src/core/digestCoverage.test.ts::canonical digest range guard::refuses ….

Refusal logging proof: src/tools/diffDigest.test.ts::diff_digest tool::records a bounded refusal without logging the caller's base expression.

Source-reading efficiency remains a live evaluation gap: [gap] verify a containing deployed build on an authorized review with a diff larger than the check output cap. Confirm complete source coverage without repeated small shell slices, then a completed current-head review. Local reader and recovery tests do not prove model choices.