Skip to content

Reading diff ​

Every PR review run carries the change as a reviewer reads it — a review_artifact run event holding the full git diff, produced by the run's own executor and guaranteed on the record. A second artifact, meat.dev's abridged reading diff (the conceptual meat of agent-written code: style, imports and mechanical noise dropped; concepts, algorithm choices and architecture kept), is produced after the review, on the bot host, over the complete diff GitHub serves for the PR — on demand through review abridge <run id>, or automatically when review.readingDiff.provider is meat. meat never runs inside an execution container, and no container ever holds the Anthropic credential: the bot spends its own key, the same one its Anthropic provider reads. This is the data half of the review panel (the run-page slideout renders it as the Files changed view).

Shaped on a measurement against the real API over one 357-line PR diff: Opus 4.8 abridged to 45 % kept in 96 s with everything kept load-bearing; Sonnet 5 kept 87 % in 240 s — meat needs an Opus-class model, and its latency is why it never sits on a review's path.

  • Code: web/src/lib/reviewAbridge.ts, web/src/lib/settingsApi.ts, src/core/readingDiff.ts (resolveReadingDiff, readingDiffStartupWarning, readingDiffCommand, parseMeatJson, capDiff, sanitizeArtifactText, produceReadingDiff, startReviewReadingDiff, READING_DIFF_CAP, MEAT_MODEL_MISSING, MEAT_TIMEOUT_S_DEFAULT); src/core/reviewAbridge.ts (ReviewAbridger, reviewAbridgerFromConfig, autoAbridgeOnPersist, AbridgeRefusal); src/core/meatProcess.ts (meatOnHost, MEAT_BINARY); src/core/commands/review.ts (review.abridge); GithubApi.compareDiff in src/execution/githubApi.ts; anthropicApiKey in src/core/anthropicApiKey.ts; the review_artifact event and PrDescriptionArtifact in src/core/runEvents.ts; src/core/reviewDescription.ts (submittedPrDescriptionArtifact, parsedPrDescriptionArtifact, findSubmittedPrDescription, startReviewDescription, SUBMITTED_LOOKUP_LIMIT); the PR facts on src/core/repoContext.ts (RepoContext.prDescription, PR_BODY_CAP); the coding post-step's publish in src/core/codingPrPostStep.ts; dispatcher wiring in src/core/dispatch/runLoop.ts; the persist hook and the shared abridger in src/index.ts; config AppConfig.review.readingDiff (src/config.ts); the binary in the bot image (Dockerfile).
  • Tests: src/core/readingDiff.test.ts, src/core/reviewDescription.test.ts, src/core/repoContext.test.ts (PR title and body for the description artifact), src/core/codingPrPostStep.test.ts, src/core/reviewAbridge.test.ts, src/core/meatProcess.test.ts, src/core/commands/review.test.ts, src/core/dispatcher.test.ts::reading-diff artifact on review runs, src/execution/githubApi.test.ts::compareDiff — the unified diff of base...head, src/core/anthropicApiKey.test.ts, src/deploy/botImageMeat.test.ts, src/core/runRecord.test.ts::capEvent on a review_artifact, src/core/authz/policy.test.ts, src/core/commandConformance.test.ts, src/core/runEventLines.test.ts, src/core/runFriction.skillUse.test.ts (side-fact invisibility), src/channels/runTimeline.test.ts; the panel: web/src/modules/pr-review/prReview.test.ts, web/src/lib/prReviewCollector.test.ts, web/src/lib/reviewAbridge.test.ts, web/src/pages/runPage.test.ts (PR-review panel wiring).
  • Web: web/src/modules/pr-review/ (the liftable module — types.ts, diffsLibrary.ts, files.ts, fileIcons.ts, FileDiffs.vue, FilesChanged.vue, LabelTip.vue, PrReviewPanel.vue), web/src/lib/prReviewCollector.ts (the adapter), web/src/lib/reviewAbridge.ts (the abridge control), the slideout in web/green/pages/Run.vue; the preview fixture scripts/web-preview-reading-diff.ts and the description in scripts/web-preview.ts (/runs/review-1).

Behavior ​

  1. The switch. AppConfig.review.readingDiff = { provider: "git" | "meat" | "off", meatModel?, meatTimeoutS? }, overridden by env SWITCHBOARD_READING_DIFF=git|meat|off (an operator flips a deployed bot without a config rebuild; an unrecognized env value is ignored). Absent everything → git: every review records the full diff and the abridged one is on demand. meat → git PLUS an automatic abridged diff once the review's record is durable (item 10). off → no artifact, so nothing to abridge either. meatModel is meat's -model, required with provider: meat — the code carries no built-in model (routing-and-config.md item 2's rule: every model comes from the configuration): the validator refuses provider: meat without it at load naming the key (validateReview); the env flip is applied at resolution, past that validator, so env forcing meat over a config with no meatModel boots — with a startup warning carrying the same missing-key sentence (readingDiffStartupWarning, printed once at boot in src/index.ts); and an abridge asked for with no --model flag and no configured value fails with MEAT_MODEL_MISSING, the sentence naming review.readingDiff.meatModel; the Opus-class floor above guides the operator's choice, it is not a default. meatTimeoutS (default 240) is meat's own runtime budget on the host (item 6). Whether abridging can happen at all is a capability (capabilities.md item 1): readingDiffAbridge is on iff the meat binary resolves on the bot host's PATH (meatOnPath, probed once at startup), the Anthropic provider's credential is set (the one getter of item 6), and the provider is not off. Computed once with the rest, printed in the startup [capabilities] line and by setup init, carried in the dashboard seed so the panel can hide the action; off → review abridge is hidden on every surface (item 9) and no abridger is built — the Null Object of an off deployment, whose defence-in-depth answer names the three facts.
  2. One command, injection-closed, git only. git diff --no-color --end-of-options 'origin/<base>...HEAD' (base falls back to origin/HEAD), the range shellQuoted into one inert token — the same discipline as diff_digest. The command never names meat: no execution container runs it (item 6), whatever the provider.
  3. The baseline never throws into the run, and honors the stream's hygiene contract. A git failure (exit N:/fatal: first line, executor throw) or an empty diff yields no artifact and never a throw. Every string the artifact carries is control-stripped and redactSecrets-redacted FIRST (sanitizeArtifactText), then capped at READING_DIFF_CAP (120 000 chars, …[N more chars], truncated: true, never splitting a surrogate pair) — a review of a PR that accidentally commits a credential must not carry it onto the run page or the record. The run record's own byte budget stays the outer bound.
  4. Guaranteed baseline — no timeout races in the pipeline. For a review run with a resolved PR, startReviewReadingDiff fires right before the model loop — the run's own executor runs the (read-only) command beside the model's tools; the event publishes straight to the registry (like input/run_meta; the status card never sees it). The dispatcher joins the baseline just before the answer publish — a join on a seconds-long command started minutes earlier, not a timeout — so every PR review's record deterministically carries a reading diff. Coding runs and PR-less reviews never produce one. The production is a background span on the run's stream (tracing.md items 4 and 18): run.reading_diff (outcome: published / none), concurrent with the loop and never a counted term; the join before the answer is run.reading_diff_join (finishing up). Nothing about the abridged diff happens in the run: with provider: meat the run still records only the git baseline.
  5. ONE abridge path. ReviewAbridger.abridge({ runId, model?, force? }) is the single way an abridged diff is produced: the review abridge command (item 9) and the provider: meat auto mode (item 10) both call it with the same inputs, so a review's abridged diff is identical whichever way it was asked for — meat itself caches by the hash of model + diff (MEAT_CACHE under <dataDir>/meat-cache), so a repeat costs nothing. The binary is spawned in exactly one module (meatProcess.ts); a structural test holds it. Its state machine, per run: absent → running → done | failed; force moves done or failed back to running. abridge on absent starts the production and answers the in-progress marker { state: "running", startedAt }; on running answers the marker again (one production per run at a time, force included); on done answers the stored artifact's summary with reused: true and spends nothing; on failed answers the remembered reason. running and failed live in the bot process — a restart forgets them: a lost job is absent again and re-runnable on demand, a lost failure is retried on the next ask — while done IS the stored artifact and survives restarts. Refusals are named and never a production failure: an unknown run is not_found, a run with no git reading diff (a coding run, a review with none) is conflict (run <id> carries no reading diff — only PR review runs record one).
  6. Input completeness — meat reads the whole diff, never a cut one. The recorded git artifact is capped (item 3) and may be truncated, so it cannot be meat's primary input: a cut diff would mislead the abridging, and refusing on it would make the feature dead exactly on the large PRs that need it. meat's input is the complete unified diff GitHub renders for base...head — GET /repos/{owner}/{repo}/compare/{base}...{head} with Accept: application/vnd.github.diff (GithubApi.compareDiff, the read credential the github tools already use; repo and head from the run's run_meta, base from the git artifact's baseRef; read up to COMPARE_DIFF_MAX_CHARS). The recorded diff serves only as the fallback, when that fetch fails (a GithubApiError — 404 for an unknown ref, 406 when GitHub will not render the comparison as a diff —, a transport error, a diff cut at the cap, an empty diff, no GitHub credential, a run_meta without repo and head) AND the artifact is not truncated; otherwise the production fails by name, spending nothing: diff unavailable: <compare failure> and the recorded diff is truncated (e.g. diff unavailable: GitHub compare failed (406) and the recorded diff is truncated). The meat artifact records which input it read — input: "github-compare" | "recorded" — and inputBytes, plus the model it ran with. meat on the host (meatOnHost): meat -json -model <m> with the diff on its stdin and exactly four environment variables — PATH and HOME from the host, MEAT_CACHE, and ANTHROPIC_API_KEY = the bot's own credential through the ONE getter anthropicApiKey(config.providers, secrets) (the first type: anthropic provider's apiKeyEnv, default ANTHROPIC_API_KEY, as a Secret from the process's Secrets — the same function the Anthropic provider's client reads; reveal()ed only into the child's environment, never process.env at a call site; PATH and HOME come from publicEnv()); none of the bot's other secrets reach meat. No credential → refused before any spawn. Failures by name: the binary missing, a nonzero exit (its code + meat's first stderr line), output that is not meat's -json shape ({smart_diff, summary, input_tokens, output_tokens}, parsed strictly), the budget passing (the child is killed: meat did not finish within Ns); every reason redacted. The binary lives in the bot image only (a static Go build from a pinned commit of boldsoftware/meat, Dockerfile, asserted with meat -h at build time); neither execution image installs it.
  7. The PR's description as data — one artifact kind, two origins. review_artifact artifact: "pr_description" = { origin: "submitted" | "parsed", repo, pr, headSha?, fromRunId?, title, body, tldr?, why?, pointers: RenderedPointer[], decisions, complete, problems, truncated } (pr-description.md items 1 and 6 for the shapes): every pointer's anchor carries the sha its permalink was rendered at, and headSha is the head the artifact was produced at — the render head for submitted, the reviewed head for parsed — so a reader compares the two to know whether a pointer points into the head it is looking at. (A record written under the previous contract carries the array as tour with remaining beside it; the line parser accepts either name, so history replays.) Persisted at its source: the coding PR post-step publishes the submitted object right after pr_opened — the same object the body was rendered from, with the PR number GitHub answered and the render head, complete: true, the rendered body — so the map as data is in the coding run's record, not only rendered into the PR. Read by every PR review: startReviewDescription fires beside startReviewReadingDiff and publishes exactly one artifact — the submitted object when the run store holds one for exactly this repo, PR and reviewed head (findSubmittedPrDescription, run-history.md item 43; its at re-stamped, fromRunId naming the coding run — an object for another head is never passed off as this head's), else the body GitHub holds (RepoContext.prDescription, captured off the resolver's one GET /pulls/{n}: title, body capped at PR_BODY_CAP = 65 536 chars with truncated) parsed back through parsePrDescriptionMarkdown with origin: "parsed", else nothing (no facts: the head fetch failed). Hygiene like the diff: every string leaf — title, body, prose, paths, notes — is control-stripped and redactSecrets-redacted; the parsed body is sanitized FIRST and then parsed, so every parsed leaf is redacted by construction; a body the resolver cut is parsed as it is with the cut named as the first problem. The store read runs in the background and the dispatcher joins it before the answer under run.pr_description_join (finishing up, like run.reading_diff_join; tracing.md item 18), so the artifact is in the record deterministically; a store failure degrades to the parsed body, never a throw into the run. Coding runs publish it only from the post-step; the switch in item 1 does not govern it (no exec is involved). The field is origin, not source: the input event's source is an object, and a literal-typed twin would make source a discriminant of the whole RunEvent union.
  8. The artifact is appended to the STORED record. meat's smart_diff and one-line summary go through the same hygiene as the baseline (item 3: sanitizeArtifactText first, capDiff after) into a second review_artifact (poweredBy: "meat", summary, meatTokens, input, inputBytes, model) appended to the finished record through the one rewrite path of run-history.md item 44 — the next seq after every event the record ever held, eventCount bumped for a new artifact and kept for a force replacement (which drops the previous meat artifact, never accumulates), the byte budget refit, the whole record put. The per-event cap leaves a review_artifact alone (its diff is the producer's to cap; its summary is one line). Readers prefer the meat artifact when both exist (item 12). A record that is gone, would not fit, or that the store does not keep is a named failed, never a resurrection.
  9. review abridge <id> [--model m] [--force] [--wait] (command-registry.md) — the on-demand path, one registration on every surface: chat, CLI, POST /api/review.abridge (body { "id": "<run id>", "model"?: "<m>", "force"?: true, "wait"?: true }), the MCP tool review_abridge. Action review:write — the command spends one Opus-class call and rewrites a stored record: admins through all, anyone granted it by name, never a baseline (review:abridge is not a word the closed action vocabulary of authorization.md item 2 has; write is the class). The run must also be one the caller may see: runs:read on the run's own attributes, like every runs.* point read — a deny is not_found, byte-identical to a missing run. Hidden without runHistory (the record it appends to). The answer is the state, never the diff (the diff is on the record: runs events <id>, the run page): { id, state: "running", startedAt } — the in-progress marker a panel polls by asking again —, { id, state: "done", reused, artifact: { model, summary, input, inputBytes, diffChars, truncated, meatTokens } } (the summary wrapped as untrusted on machine surfaces: it is model prose), { id, state: "failed", reason, at }. --wait blocks for the outcome (the CLI's shape); in chat the acknowledgement is the marker and the outcome lands as a second reply through settle (command-registry item 26). Refusals: not_found (unknown or invisible run), conflict (no git reading diff), unavailable (no run history).
  10. Auto mode is the same path, after the record is durable, detached. With provider: meat, the run-history writer's onPersisted hook (the final record's successful put — never the provisional tombstone) calls abridge({ runId }) through autoAbridgeOnPersist as a detached host task: the review's reply, its record and its finish are already done, so the pipeline never waits on meat and nothing about a review changes with the switch except that its record later gains the meat artifact. Refusals are silent (every non-review run persists through the same hook); a failed outcome is warned with the run id and reason ([reading-diff] <id> meat did not land: <reason>). git and off do nothing here. The shared ReviewAbridger (one per process, src/index.ts) means an operator's review abridge during an automatic production sees running, not a second spend. A restart mid-production loses the job (item 5): the run is abridged on demand later; nothing is retried at boot.
  11. A side fact, not a step. The friction analyzer treats review_artifact — both kinds — like skill_use: invisible to eventCount, steps and turn timing; parseRunEventLines accepts it (reading_diff + string diff + known poweredBy; pr_description + known origin, string repo/title/body, numeric pr, an array pointers or, on an old record, tour); the timeline deliberately folds both to nothing until the panel renders them.
  12. The pr-review module renders it (web/src/modules/pr-review/ — a deliberately liftable, props-only Vue module; see its README). Contract: PrReviewData = { pr: PrRef, readingDiffs: ReadingDiff[], description?: PrDescriptionData } plus an optional AbridgeControl prop — the module imports nothing from the app's lib/pages or anything runs-shaped (one generic component, MarkdownText, excepted and named in its README), so the whole folder can be shared into another product with the host writing its own adapter. PrReviewPanel is a header, a tab row and the tab's content, top to bottom, scrolling the content under the other two. The header: the PR's title (data.description.title when the host knows it, else owner/repo#N); one line of facts — the reference with the GitHub mark linking to the PR, the head sha linking to the commit, against origin/<base>, the producer label, truncated when the producer capped the diff — every GitHub link built only from shape-verified values (prLinks: owner/name + positive integer + 7–40-hex sha; anything else renders as text); the producer label and truncated carry a tooltip that says what they mean (poweredByExplanation, truncatedExplanation — the latter names the character count the recorded diff was cut at; one setting for all, LabelTip: wraps at a measure, opens below, collision-aware, a short delay); at the right, the actions — View on GitHub as a ghost button, then the abridge control while only the full diff exists and the host offers one (below), then a close control for a closable host. The tab row: one tab per diff on record — Files changed for the full git diff, Reading diff for the abridged one, the reader landing on the abridged one when a producer made it (preferredDiff) — and Description when the host's description carries prose; nothing to show → no row. At the row's right while a diff tab is open and the panel is at least 42rem wide: Inline / Side by side, remembered per browser (localStorage key switchboard:diffStyle; a blocked store keeps the choice for the panel). Files changed (FilesChanged) is two columns. Left, the file list (diffStats, the library's own parser): N files and +A −D in its head, then one row per file — its language icon (fileIcon: a known file name first, then the extension, else the plain file), its path cut in the middle when it runs past 34 characters (ellipsizeMiddle — the folder and the file name are what a reader recognises; the whole path is the row's title), its +A −D; clicking a row marks it current (aria-current) and scrolls its diff into view (scrollIntoView on the file's data-file anchor), the list staying put. Right, the diff column: the host's notes as muted lines over the diff (the abridged diff's one-line summary; the cut's notice), then every file through @pierre/diffs (FileDiffs: one diffs-container per file carrying its path as data-file, the library's own header — change icon, path, counts — over its hunks, with the unmodified-lines expanders, the word-level emphasis and the syntax highlighting, inline or split, in the app's colour mode; re-rendered when the diff, the theme or the style changes; the library loaded once, on demand, through diffsLibrary.ts; a parse that yields no file renders nothing (the view says so in its own words); a renderer that throws leaves the diff shown as plain text — never markup). Below 672px of the component's own width the list folds above the diff behind a N files changed row (open on click, closed by a pick) and the diff reads inline whatever the choice. Description: the TL;DR and, under its own heading, the Why as markdown through MarkdownText; a muted note when the copy was read back from the PR body and came up short, or was cut before it was read (descriptionNote). Empty states: no diffs and no description → one sentence, no tab row; a diff that parses to no files says so. The slideout's accessible name is the hidden dialog title (panelTitle), since the panel replaces the dialog's visible header. Switchboard's adapter is web/src/lib/prReviewCollector.ts — the one place run shapes meet the module: run_meta → the PR identity, review_artifact reading_diff → the diffs (one per producer, a later artifact replaces its producer's earlier one — a same-run re-review), review_artifact pr_description (item 7) → description (descriptionFrom: title, origin, complete, truncated; the TL;DR is the artifact's or the body's first paragraph (firstParagraph), the why is the artifact's, else the body's **Why:** field (mapField, up to the next label, the pointer list or the first fold, fences respected), else a legacy body's ## What & why section (markdownSection); the pointers stay on the record unread — the PR body renders them; a later artifact replaces the earlier; a malformed one — a bad title or origin — changes nothing; a description alone lights nothing), malformed frames ignored. The run page feeds the collector the same frames as the timeline (seeded history and live SSE alike) and, once ready (at least one diff), shows a Reading diff button on the run-meta line that opens the panel in a slideout. Coding runs and reviews with the provider off never light the button. The abridge control (web/src/lib/reviewAbridge.ts, item 9's surface from the page): on a stored run when capabilities.readingDiffAbridge is on, the page hands the panel a control; the panel shows it among the header's actions while the git diff stands alone — the Abridge with meat button, the one solid button (tooltip: what it does, one model call), then Abridging… usually 1–3 minutes with a spinner, or the failure's reason with a Retry; start() POSTs /api/review.abridge { id } as JSON same-origin ({ id, force: true } only on a retry after the server's own failed — a stored failure is what force recomputes; a transport or page-side failure is retried without it, so the command's idempotency answers a stored artifact instead of spending a new call), reads running and polls with the same POST every POLL_INTERVAL_MS up to MAX_POLLS, and on done pages GET /api/runs.events?id=… into the collector — at most MAX_PAGES pages, and a cursor that does not advance ends the read as a named failure — so the meat artifact lands like any other frame and a Reading diff tab takes the control's place, selected; a non-2xx answer fails with the body's error (else HTTP <status>), a thrown fetch with its message, a done with no meat artifact on the record names that; dispose() on unmount stops the polling, and no frame read after it reaches the collector. Capability off, or both producers present → nothing renders (no disabled button).

Deploy prerequisites for the abridged diff ​

Built in: the meat binary ships in the bot image. What an installation supplies: run history (the record the artifact is appended to), the Anthropic provider the bot already has (meat spends its key), and a GitHub credential (the App, or GH_TOKEN) for the compare diff — without one, only reviews whose recorded diff is whole can be abridged. provider: meat is then one config line (or SWITCHBOARD_READING_DIFF=meat).

Roadmap (gaps) ​

  • [gap] The panel's button: review abridge on the run page (the HTTP form of item 9, polled until done), and the meat artifact's input/inputBytes shown beside its badges — the web module is owned separately.
  • [gap] Landing rate: a count of failed productions and their reasons on the runs index or in friction report (today: the warn line of item 10 and the artifacts on the records).

Validation criteria ​

CriterionProof
Resolve: default git + default meat budget; config selects provider/model/budget (floored, positive); off → null; env overrides config; unknown env ignored; meat without a configured model names none — no built-in model, the missing-key sentence names review.readingDiff.meatModel — provider: meat without the key is refused at load by name, and env forcing meat past that validator with no configured model warns at startup with the same sentence[unit] src/core/readingDiff.test.ts::resolveReadingDiff (config + env)::* (5), src/config/validate.test.ts::validateReview — the reading-diff switch::* (2)
Command: exact git string, origin/HEAD fallback, hostile ref quoted inert, never names meat[unit] src/core/readingDiff.test.ts::readingDiffCommand::* (4)
meat JSON parsed strictly; error line / non-JSON / no smart_diff throw[unit] src/core/readingDiff.test.ts::parseMeatJson::* (2)
Cap with note + truncated flag; never splits a surrogate pair[unit] src/core/readingDiff.test.ts::capDiff::*, src/core/readingDiff.test.ts::produceReadingDiff::caps the diff…, ::never splits a surrogate pair at the cap
Stream hygiene: secrets redacted + ANSI stripped before the cap, on the baseline and on meat's diff and summary[unit] src/core/readingDiff.test.ts::produceReadingDiff::redacts secrets and strips ANSI…, src/core/reviewAbridge.test.ts::ReviewAbridger — failures and the append::the appended diff and summary are redacted…
Baseline producer: git artifact; git failure / executor throw / empty diff → null, never a throw[unit] src/core/readingDiff.test.ts::produceReadingDiff::* (6)
startReviewReadingDiff: the baseline publishes and resolves true; provider: meat still runs ONE git command and publishes only git; off runs nothing; a failure resolves false, never rejects[unit] src/core/readingDiff.test.ts::startReviewReadingDiff (the baseline, guaranteed)::* (4)
Wiring: a review of a resolved PR carries the baseline with seq before the answer's; provider: meat → the record carries git only and no meat command reaches the executor; off and coding runs publish none[unit] src/core/dispatcher.test.ts::reading-diff artifact on review runs::* (4)
Spans: the baseline is a run.reading_diff child of the root with outcome published/none and the diff's exec under it; without a root nothing is measured (tracing.md item 18)[unit] src/core/readingDiff.test.ts::reading diff spans (docs/reference/specs/tracing.md items 17/18)::* (3)
5 — one path: abridge runs absent→running→done and appends the meat artifact; idempotent (done/reused, no spend) until --force, which replaces; a second call while running is running, --force included; unknown → not_found, no git artifact → the named conflict; the persist hook calls the same abridge[unit] src/core/reviewAbridge.test.ts::ReviewAbridger.abridge — the one path::* (4), src/core/reviewAbridge.test.ts::autoAbridgeOnPersist — provider: meat is the same path, after the record is durable::meat → abridge({ runId }) on persist…
5 — one spawn site: no source file other than meatProcess.ts spawns the binary[unit] src/core/meatProcess.test.ts::meat is spawned in one module::no other source file names the binary as a child process
6 — input order: the compare diff first (input: github-compare, inputBytes); 404 → the whole recorded diff with a warning; 406 + truncated → the named refusal, nothing spent; a diff cut at the cap, no credential, a run_meta without repo/head, a transport error → the same order and wording[unit] src/core/reviewAbridge.test.ts::ReviewAbridger — input completeness (compare first, recorded only when whole)::* (5)
6 — compareDiff: GET /repos/<repo>/compare/<base>...<head> with the diff media type on the read token, percent-encoded refs, cut at the cap with complete: false, GitHub's 404/406 as GithubApiError; the in-memory double answers seeded comparisons[unit] src/execution/githubApi.test.ts::compareDiff — the unified diff of base...head::* (5)
6 — meat on the host: the diff on stdin, -json -model <m>, exactly PATH/HOME/MEAT_CACHE/ANTHROPIC_API_KEY in the child env; no credential → refused before a spawn; missing binary, nonzero exit (code + first stderr line), non-JSON, the budget (killed, named) → named failures; reasons redacted[unit] src/core/meatProcess.test.ts::meatOnHost::* (8)
6 — the one credential getter: the first anthropic provider's apiKeyEnv (default ANTHROPIC_API_KEY); undefined without a provider or an unset/empty variable, never a fallback[unit] src/core/anthropicApiKey.test.ts::anthropicApiKey — the one credential getter::* (2)
6 — the binary in the bot image only: a pinned Go stage, CGO_ENABLED=0 go install meat.dev/cmd/meat@<40-hex sha>, one COPY --from=meat, command -v meat + meat -h asserted, copied before USER switchboard; neither execution image names meat[unit] src/deploy/botImageMeat.test.ts; [agent] CI image / build bot (check:image) green at the PR head
8 — the append: next seq after every existing event, eventCount bumped for a new artifact and kept for a replacement, the summary survives the event cap, a gone record / stored: false → named failures[unit] src/core/reviewAbridge.test.ts::ReviewAbridger — failures and the append::* (4), src/core/runRecord.test.ts::capEvent on a review_artifact::does not shrink the summary…
9 — the command: running marker then done summary (untrusted-wrapped, the diff never in the answer); --wait; --force + --model reach abridge; conflict / not_found wording; the run must be visible (a private run is not_found to an outsider holding the grant); no grant → unauthorized before anything runs; no history → unavailable; settle posts the outcome after a running ack; hidden without runHistory[unit] src/core/commands/review.test.ts::review.abridge::* (9)
9 — the policy row review:write command [has-grant(review:write)]: admins allowed; a plain chat user, a member, a run-scoped token, no grants, dispatch-only denied[unit] src/core/authz/policy.test.ts (the row's allow/deny cases; coverage)
9 — every surface: the derived forms, the MCP schema, --help, the authorization matrix and identical invoke JSON for review abridge <id> and each option variant[unit] src/core/commandConformance.test.ts (registry-driven; the seeded review rev-1)
10 — auto mode: provider: meat → abridge({ runId }) on persist; git/off (config or env) → nothing; refusals silent; a failure warned with id and reason[unit] src/core/reviewAbridge.test.ts::autoAbridgeOnPersist — provider: meat is the same path, after the record is durable::* (2)
Stream + friction: accepted when well-formed, skipped otherwise; diagnosis identical with/without side facts[unit] src/core/runEventLines.test.ts::…::accepts \review_artifact`…; src/core/runFriction.skillUse.test.ts`
Module: prLinks builds links only from shape-verified values (hostile repo/sha → text, never a URL); preferredDiff abridged-first; panelTitle title → reference → repo → label; the label explanations and descriptionNote[unit] web/src/modules/pr-review/prReview.test.ts::prLinks::*, ::preferredDiff::*, ::panelTitle::*, ::the labels' explanations::*
12: the files — diffStats counts added and removed lines per file (the new path, the old one for a deletion) and in total, zeros for text that is not a diff; ellipsizeMiddle keeps both ends within the budget; the language icon by file name, then extension, else the plain file; the library's one dynamic import is paid once before the file under its own 30 s budget, so no test's 5 s default carries it[unit] web/src/modules/pr-review/prReview.test.ts::files::*
12: the panel — the title, the facts (reference, head, base, producer, truncation, the GitHub button), the reading diff first with its summary and cut as notes, the file list with counts and icons and the files handed to the renderer under their data-file anchors; a known title becomes the header and the close control renders only for a closable host; one tab per diff on record plus Description when there is prose, a lone full diff one tab; Files changed shows the full diff; the file list cuts long paths in the middle and a click marks the row current and scrolls its diff into view; Inline / Side by side re-renders, is kept per browser and read back on the next mount, and is absent on the Description tab; the Description tab renders the TL;DR and What & why as prose with the note only for a copy that is less than the whole; the empty states; a repo without a valid number is text and no GitHub button[unit] web/src/modules/pr-review/prReview.test.ts::PrReviewPanel::*
12: the fold and the renderer's failure — under 672px the list folds behind a N files changed row that opens on click and closes on a pick while the diff reads inline whatever the style, and wide it is a column with the count and totals in its head; a renderer that throws leaves the diff as plain text, hostile text staying text; the diff, the theme and the style each re-render, one container per file[unit] web/src/modules/pr-review/prReview.test.ts::FilesChanged — the two columns fold on a narrow panel::*, web/src/modules/pr-review/prReview.test.ts::FileDiffs — the renderer's own failure::*
12: the abridge control in the panel — no control or both producers → nothing; absent → the solid button beside View on GitHub with its tooltip, a click starts; running → spinner + Abridging… usually 1–3 minutes, no button; failed → the reason + Retry that starts again; the meat artifact arriving on the data replaces the control with a Reading diff tab, selected[unit] web/src/modules/pr-review/prReview.test.ts::the abridge control::*
Adapter: run_meta → PR identity, review_artifact reading_diff → one diff per producer (later replaces earlier), pr_description → the description (title, TL;DR from the artifact or the body's first paragraph, the why from the artifact or the body's **Why:** field or a legacy ## What & why section, origin, completeness, the cut; the pointers unread; later replaces earlier; a bad title or origin changes nothing; never ready on its own), malformed frames ignored, never throws; ready only once a diff exists; firstParagraph, markdownSection and mapField[unit] web/src/lib/prReviewCollector.test.ts (8)
The page's abridge action: start POSTs { id } as JSON same-origin, polls read-only status after a delay, on done pages the record into the sink; a reused done skips the poll; done with no meat artifact fails naming it; failed carries the reason and a retry after the server's failure sends force: true, after a transport or page-side failure no force; a non-2xx fails with the body's error else the status, a thrown fetch with its message, an unknown state too; start while running is a no-op; dispose stops the polling and no frame reaches the sink after it; MAX_POLLS rounds give up; paging stops on a cursor that does not advance or past MAX_PAGES, a named failure[unit] web/src/lib/reviewAbridge.test.ts (8)
Wiring: history seed and live SSE both light the Reading diff button exactly when artifacts exist; the slideout renders the panel with the PR link, the file changed and its diff handed to the renderer, and the dialog's aria-labelledby resolves to a rendered title; a seeded pr_description names the dialog by the PR's title and its Description tab carries the TL;DR; with readingDiffAbridge on and only the git diff the panel offers Abridge with meat and a click POSTs /api/review.abridge for this run, off → nothing[unit] web/src/pages/runPage.test.ts::PR-review panel wiring (5)
Live: a review run's GET /runs/:id/events carries one review_artifact poweredBy: "git" with the PR's real diff; review abridge <id> on it answers running then done with input: github-compare, and the record then carries a poweredBy: "meat" artifact the panel shows as a second tab[agent] post-deploy: agent:review <PR URL>, then review abridge <run id> --wait from Slack or the CLI, then probe the run's events.
7: the submitted builder carries the golden's title, tldr, tour (anchors stamped with the render sha), remaining, decisions and the rendered body, complete; every string leaf — title, prose, anchor path, decision, body — is control-stripped and redacted[unit] src/core/reviewDescription.test.ts::submittedPrDescriptionArtifact::carries the golden's title…, ::every string leaf is control-stripped and redacted…
7: the coding post-step publishes the submitted artifact right after pr_opened with the PR number GitHub answered, the render head on every anchor and the very body it put on GitHub; no PR opened → no artifact[unit] src/core/codingPrPostStep.test.ts::runCodingPrPostStep (callable with explicit inputs)::the golden description → a \submitted` pr_description artifact…, ::no PR opened (open throws, or nothing to open) → no pr_description artifact`
7: the PR facts ride on the repo context from the one GET /pulls/{n} — explicit and inherited PR alike; a null body is empty; the body is capped at PR_BODY_CAP with truncated, never splitting a surrogate pair; no title or a failed fetch → unset[unit] src/core/repoContext.test.ts::PR title and body for the description artifact::*
7: the parsed builder: the golden body parses back complete with the reviewed head beside the anchors' render sha; sanitized BEFORE parsing (a token never reaches tldr, a step or the body; ANSI stripped); a shapeless body → first paragraph, empty tour, complete: false; a truncated body names the cut first[unit] src/core/reviewDescription.test.ts::parsedPrDescriptionArtifact::*
7: the lookup finds the newest submitted artifact for exactly this repo, PR and head across the repo's records — skipping parsed copies, other PRs, heads and repos — a review run's copy counting with the coding run kept as fromRunId, the newest within a record winning, bounded by SUBMITTED_LOOKUP_LIMIT[unit] src/core/reviewDescription.test.ts::findSubmittedPrDescription::*
7: startReviewDescription prefers the stored object for the reviewed head (once, re-stamped, fromRunId), parses the body when none / another head / the store throws, publishes nothing without a PR or facts, contains a throwing publish — never rejects[unit] src/core/reviewDescription.test.ts::startReviewDescription::*
7: wiring — a review with facts and no stored object carries the parsed artifact before the answer (redacted, reviewed head beside the anchors) beside the diff; a stored object for the reviewed head is published instead, re-stamped, naming the coding run; one for another head → parsed; no facts → the diff alone; a coding run publishes none at start[unit] src/core/dispatcher.test.ts::reading-diff artifact on review runs::no submitted object in the store…, ::the store holds the submitted object for the reviewed head…, ::the store's submitted object is for ANOTHER head…, ::no PR facts (the head fetch failed)…, ::a coding run publishes no artifact — neither…
7: stream + side fact — parseRunEventLines accepts the kind (a pointers array, or tour on an old record) and skips a bad origin / pointers / pr; the analyzer's diagnosis is identical with both artifact kinds present; the timeline folds it to nothing[unit] src/core/runEventLines.test.ts::…::accepts a \pr_description` review_artifact…; src/core/runFriction.skillUse.test.ts::…::a review_artifact — the reading diff and the pr_description alike — is invisible to friction; src/channels/runTimeline.test.ts::…::folds a pr_description artifact to nothing too…`
7: live — a review of a PR opened by agent:coding carries pr_description with origin: "submitted" and fromRunId = the coding run; a review of a hand-written PR carries origin: "parsed" with its pointers' anchors at the body's permalink sha[agent] post-deploy: agent:review <PR URL> on one PR of each kind, then probe GET /runs/:id/events for the artifact.

review status <id> / GET /api/review.status reads the current abridgement without starting or retrying it. The run visibility gate still applies. The page starts through the shared confirmed web write path, then polls this read-only command.

CriterionProof
Polling does not start or recompute an abridgement[unit] src/core/commands/review.test.ts::review.abridge::review.status reads progress without starting or recomputing the abridgement