Group a git diff into review concerns and serve a local UI. The point is cognitive offloading over cognitive surrender: the human gets a why and a what of their own to hold against the live diff, instead of adopting the change blind.
Run every command inside the repository under review. Cwd is the repo; there is no --repo flag. Use the pinned CLI:
npx comprehende@0.10.1 <command>
The review document is interpretation only. It holds a title, groups, summaries, and hunk pointers. Do not copy patch text into it. serve reads the diff from git.
Work from the digest, not the diff. The digest is one line per file: kind, hunks, symbols, pairing and copy hints. Grouping needs that line; only ambiguity needs patch text. Fetch patch with show, batched in one call and scaled to the digest.
Workflow
Each step is one tool call where the step says so. Batch the commands as written; every extra round trip re-sends the whole context. Spend one tool call per phase: the step-2 batch, one sources call, one show batch, one write, validate. Further calls only fix what validate names.
-
Name base and head. Use the refs the user named; three-dot (
base...head) is the merge request or branch diff. When the change is already on the default branch, use the request's recorded base and head SHAs; the moving default-branchHEADincludes later merges. When only the head SHA is known, base is the merge-base of that head with the named base branch. Fetch a ref that is missing from the local clone. Done when both ref names are chosen. The next shell call verifies them. -
One shell call: verify the refs, write the draft, print the digest and the draft, log, and stat.
$REVIEW_DIRcomes frommktemp -din that call (outside the repository; the work tree stays untouched, with no new gitignore entries).&&stops the shell beforepregroupwhen a ref does not resolve. Stop and tell the user which ref failed, rather than guess a ref.REVIEW_DIR=$(mktemp -d) && git rev-parse --verify --end-of-options "<base>^{commit}" && git rev-parse --verify --end-of-options "<head>^{commit}" && { npm view comprehende version || true npx comprehende@0.10.1 pregroup --base <base> --head <head> --data "$REVIEW_DIR/review.json" npx comprehende@0.10.1 digest --base <base> --head <head> cat "$REVIEW_DIR/review.json" git log --format='%s' --end-of-options <base>...<head> git diff --stat <base>...<head> }When the user names a local executable, use that command as written in place of
npx comprehende@0.10.1, and run<executable> --versionin this same shell call in place ofnpm view. That printed line is the CLI version.If that version is newer than this pin (
npx comprehende@0.10.1), stop and tell the user. Shownpx skills updateas an option they can run. Do not run that command. Wait for them to continue with this pin, or to update and start this skill again. If the versions match or the query fails, continue. Defaults:--headisHEAD;--baseisorigin/HEAD, falling back tomainormaster.pregroupwrites a draft: identical-copy clusters, onemechanicalgroup (renames, copies, moves, generated output), and onechangegroup for the rest, with stub prose. It does not invent a review. Done when that file exists and the digest and draft are in context. Trust theidentical copyandpairs withhints; never fetch patch to confirm them. Never read the draft from disk; thecatabove already printed it. -
One tool call for all sources, truncated at fetch time. Files on disk go through one shell call that caps each file, never a read per file:
for f in "<sourcesDir>"/*; do echo "=== $f"; head -n 40 "$f"; doneSources behind a host CLI or MCP go in one parallel block, one fetch each: PR or ticket body to 15 lines, each review comment to 10 lines with whether its thread is resolved, whether the comment is outdated, and its current line, commit log subjects only. Never run a full
git diff; the digest line is the file. Full bodies stay out of context; the caps above are the source. Done when the digest, the draft, and those sources are in context. -
Recover the why, write the title, write the what, and check each source item against the digest and the fetched hunks (see lookFor). Then write document
title(always), documentsummary(always), documentwhy(only when a source names the motive), and documentlookFor(only when an item diverges; see lookFor). Summaries come from the digest symbols and kinds, plus fetched hunks where fetched. Replace the stub title and summary. Done when every source item has a verdict, every divergence is a documentlookForbullet, every ticket item a later comment changes is a bullet, and every lookFor bullet matches the code at head. -
Group the hunks by review concern, starting from the draft. Accept a draft group that already holds one concern. Split the
changegroup where a reader would stop and review the first piece alone. Merge draft groups that serve one concern. A path covers every hunk of its file: keep the draft's path refs, and write hunk refs only for a file split across groups. Fetch patch only to decide a split: state the decision first ("login.tshunks may belong apart"), then batch every fetch in the one show call below. Fetch at most one hunk per 3 files in the digest (at least 2, at most 8). Draftmirrors,mechanical, and one-concern groups need no fetch:npx comprehende@0.10.1 show --hunk "<ref>"; npx comprehende@0.10.1 show --file "<path>"Copy each ref from the draft into the group that holds it, verbatim; the draft refs already match live git. When one file's hunks belong in different groups, name each hunk as in the hunk identity paragraph. Set document
sizefrom review burden, notgit diff --stat. Write the wholereview.jsonin one write from the field shape below. Keepversionandsourcefrom the draft. Done when every ref from the draft is in at least one group, every live hunk of a split file is named, every group has itswhy, stub prose is gone, and every namedparthas a matching documentparts[]entry. Also done when each unit test sits in the group of the code it checks, each new harness sits with the build or CI that runs it, each feature spec sits with its seed and helpers, each one-line call site or declaration sits with the code it serves, unasked work that could have been its own PR has its ownpart, related enabling work stays one part, eachdependsOnid names a group in the samepart, and each mechanical summary says what the reader can skip. When a story builds on another part,suggestedOrderputs that part first.Field shape. The words are placeholders. This shape is enough to write a valid document.
{ "version": 1, "source": { "baseRef": "<base>", "headRef": "<head>", "range": "<base>...<head>" }, "size": "small", "title": "Short name", "summary": "What the change is.", "groups": [ { "id": "helper", "title": "Group title", "why": "Why this group exists.", "summary": "What this group is.", "suggestedOrder": 0, "hunkRefs": ["src/file.ts"] } ] }Add a field when the section that defines it says to write it: document
why,lookFor, andparts(name,summary);sources(id,kindofticket,pr,pr-comment,commit, ortranscript,label, plusurl,title,gist, andpartwhen you have them). On a group:part,sources(those ids),lookFor,dependsOn(group ids). Apr-commentsource also hasauthorandbody. A line pin addspath,side(oldornew), andlinetogether, and only for the pin case under PR comments.sizeistrivial,small,medium,large, orvery-large. -
Run
npx comprehende@0.10.1 validate --data "$REVIEW_DIR/review.json"with the absolute path. It checks exactly these: every live hunk sits in a group, every ref matches live git, everysource:id exists insources, every groupparthas aparts[]entry, every PR comment pin (path,side,line) matches a live line, and the document has no unknown fields. On failure, fix what the message names; the diff is git's, leave it alone. Done when validate exits 0. -
When they ask to upload the report, follow Export. Otherwise run
npx comprehende@0.10.1 serve --data "$REVIEW_DIR/review.json" --openand give the user the localhost URL (127.0.0.1only).
references/example.md is an optional filled sample of the field shape. The shape in step 5 is enough to write a document.
Export
Write a static site and put that folder where they asked.
npx comprehende@0.10.1 export --data "$REVIEW_DIR/review.json" --out "$EXPORT_DIR"
$EXPORT_DIR is a fresh directory outside the work tree, not a git repository. The folder is the UI plus frozen git payloads. There is no git in it. Done when they have the URL or path they named. If review.json is still a draft, finish the Workflow through validate, then export.
The title
Document title is a short name for the whole change. Always write one.
Prefer a title the human already wrote when it names this change: the pull request title, a ticket title, or the name they used in a transcript. Keep that wording when it is descriptive and represents the reviewed change.
Invent a title when those sources are missing, vague, or name something else. A branch slug, "WIP", or "fix" is not a title. Sources are a reference. Do not paste a title that does not fit.
The why
Document why names why the work exists, in one or two sentences, inferred only from the sources below. It is the problem or goal a source states. Before writing it, find the source sentence that states that problem or goal: "Support SSO login", "so admins can export audit logs", "retries flood the API". A sentence that only says what the change does ("the settings page becomes a modal", "uploads move to S3") belongs in the summary. When no source sentence states a problem or goal, or the sources are mixed, omit the why. Write it in the source's own terms, with no cause, benefit, or reader need the source does not name. The same holds for every group why: a citation carries only what that source says, and a source is the reason for a choice only when it states that reason. Do not derive document why from the digest, from group structure, or from a motive you infer from the patch. A request naming two unrelated product stories with no unifying source also gets no document why; each story keeps its own group whys.
Sources are read truncated, never copied into the review document, except PR review comments: copy author and body so the UI can show the quote. Keep ticket bodies, request descriptions, commit text, and transcript text out of review.json. Cite a source in prose with [text](source:s1) when that sentence leans on it.
- Tickets and issues. Emit a
sourcesitem: shortid(s1),kindticket,label(#24), plusurl,title,gist, andpartwhen you have them. Read the ticket (host CLI, tools or MCP, whichever is available) when documentwhyneeds it.partgoes on a ticket that belongs to one story, matching that story's grouppart; omit it only when the request or ticket title says the ticket covers the whole review. Commit count proves nothing about ticket scope. Do not copy the ticket body. - Pull request. When the user named a pull request or merge request, emit
kindprwith a label likePR #32and a gist from the description. Do not copy the description. PRs can also refer to tickets and issues in their description or branch names. - PR comments. Emit
kindpr-comment. Label likealice on PR #32. Copyauthorandbody. Flatten threads: one source per comment. Pin a comment that still sits on one line at head and whose thread is open. Copypath,side(oldornew), and the forge's current line. GitHubLEFTisold,RIGHTisnew, and the current line isline(the line on an older commit isoriginal_line). A conversation comment has no pin. A resolved thread (isResolved) has no pin. An outdated comment has no pin (isOutdated, or the current line is null). When an unpinned comment still asks for work the change does not do, cite it in lookFor. - Commit messages.
git log --format='%s' --end-of-options <base>...<head>. Emitkindcommitwhen a commit names the motive.labelis that subject line, or a SHA from the same log. They can clarify groupwhy; documentwhyneeds one of the stronger sources above. These can also contain issues and tickets. - Transcripts. A coding-agent (like Cursor, Claude Code, Codex, etc.) transcript you already have (this session, or a historical one) supplies the human's stated reason. Emit
kindtranscript, a label likeCursor session · Aug 12, and a gist. Omiturl. Do not copy transcript text.
Put those ids on the group sources array when the group uses them, even if the prose does not cite them inline. validate fails on a source: href whose id is missing from sources.
Every group has why: why this group exists. A source that names the concern is best. A foundation group with no source of its own says it enables the groups that depend on it; that is a complete why. Independent side work in its own part (docs-only, test-only) gets its own group why and leaves document why to the named motive.
The what
Document summary is one or two sentences naming what the change is, including each independent story when the PR is mixed. Every review has one, even when why is absent. It is a what, not a motive.
When groups use part, write one document parts[] entry per independent story. name is that part string. summary is one sentence saying what that story is, same voice as group summary. Document summary still names the stories; the part summary is the per-story what under the colored Overview column. Omit parts when no group has part.
Group summary is one sentence saying what the group is, describing how its hunks are related: "Calling the new validator from core.ts by the route in routes.ts", not a file-by-file recap or a path list. For draft mirrors and mechanical groups kept as-is, the summary says what the reader can skip and why that is safe: an identical copy, a pure move, or regenerated output that means the same.
Grouping rules
- Group by review concern: why these hunks are read together. A directory qualifies only when that directory is the concern. The digest line plus the draft is enough for most files; the patch is for ambiguity only.
- A group is one concern a reviewer names in a phrase. The code, the unit tests that check it, and the one-line call sites, declarations, and re-exports that serve it read together in that group. A unit test next to its module stays in that module's group; the digest
pairs withhint names that module. A new integration harness is the concern: that harness and the build or CI files that run it are one group, not the production group they exercise. A new feature's code, fixtures, seeds, helpers, and the spec that runs them are one group. A seed or helper that only one spec uses stays in that spec's group. A foundation split is for a contract several later groups share. Split a concern only where a reader would stop and review the first piece alone. A small change has one to three groups. - A hunk may appear in several groups when it matters in more than one story. When one hunk holds two concerns, the group that holds it names both.
dependsOnmarks a real dependency inside one story: the reader needs the earlier group to understand this one. It stays inside one part. Reading order inside a story: contracts and foundations, then call sites, then mechanical work. Independent work stands alone with its ownpart, emptydependsOn, and nothing depending on it. A second feature, extra product events, a dependency policy change, or standalone documentation that could have been its own PR is independent work. Unasked work that only enables the asked story (path renames, fixture layout, local startup) stays one part with that story, or one harness part. When unsure whether two concerns depend on each other, keep them separate under different parts: a false split is easy to see, a false chain hides a mixed PR.partis a short name (a few words) for one independent story, the same on every group in that story; the UI colors shared parts together. One documentparts[]entry per name.- Mechanical work (import reordering, identifier-only renames, generated code, formatting, pure moves) is its own group that keeps every hunk as refs, risk visible rather than folded into a file list. The draft
mechanicalgroup already holds the CLI-flagged files; check each line still belongs (a flagged file with a real behavior change moves out to its story) and write the summary that says what the reader can skip. An identical copy of a file in the draftmirrorsgroup sits with its source; that group's summary still says the copy is identical. When a mechanical group exists only because of a story, it is that part's last group with nothing depending on it. When it could have been its own PR, it is its own part. suggestedOrderwalks the whole review, independent parts included. Mechanical parts, independent documentation, and test-only cleanup go last.- Document
sizeis human review burden:trivial,small,medium,large,very-large. Forty files changing one import in one group aresmall. A new component plus follow-up changes across existing screens ismedium. A new contract that later groups depend on islarge. Thatsizestays when those groups merge. The digest header (file and hunk counts) sets the starting point; the group count and dependencies adjust it. - Coverage: every live hunk sits in at least one group. A path covers every live hunk of that file. Duplicate refs across groups are allowed. Unreferenced hunks fail
validateand show as Unassigned in the UI. Every ref in the draft already covers its hunks; splitting a file across groups needs hunk refs, everything else keeps the draft's paths. - Lockfiles have no hunk refs; the CLI leaves them out of the draft and the UI gives them their own closed bucket. Hunk refs exist only for hunks the draft lists.
- A path stays valid when a rebase only shifts line numbers.
path@oldStart+newStartgoes stale when those starts move, and that failsvalidate.servestill starts, shows git at those SHAs, and flags the broken pointer. Re-runpregroupand copy fresh refs. A dirty work tree does not make refs stale.
A hunkRefs entry is a path string when the group holds every live hunk of that file. Copy the path from the draft. When a file is split across groups, name each hunk as path@oldStart+newStart, or old/path -> new/path@oldStart+newStart when that hunk is a rename. Take oldStart and newStart from the digest show of that file, or from the hunk's @@ -oldStart,oldLines +newStart,newLines @@ header. Identity is (path, oldStart, newStart) plus oldPath when renamed. Image files are paths. Git LFS images are read from .git/lfs/objects in the clone; a missing object leaves the image slot empty.
lookFor
lookFor is for what a careful engineer would miss on a first read of the live diff. Default to an empty lookFor and earn each bullet with a fetch: a bullet lives only where you ran show on its hunks, or where the digest plus the sources already prove it. Each bullet is one finding, stated as fact and true at head: optionally a short risk tag (Subtle, Breaking, Race, Perf), then one sentence on what the code at head does and what the reader would observe. The human checks it against the live diff and keeps the verdict. Pseudocode, diagrams, collapsed diffs, commit lists, hunk counts, and pass/fail marks stay out.
Group lookFor holds claims that live in those hunks. Fetch the hunk when a behavior change hides in it and write the predicted trace: a small realistic input, where the old and new paths diverge, and the observable result. A change in equality or identity (object versus primitive fields, pointer versus contents) uses two inputs that look the same and now compare differently. When a hunk works around app behavior (a sleep, a forced write, a retry), the bullet says what the app does without the workaround. A hunk under application source in a PR whose sources are about tests, CI, or tooling is production code in a test PR: the bullet says it is production code and names that behavior change. A setup or teardown script that deletes or overwrites a file that is not test output earns a bullet: the path, when it happens, and what the developer loses. So does a behavior change no source mentions, and a hunk that resolves a review comment in a way the hunk alone does not make obvious: the bullet says what the fix does. Empty is correct for a rename, a formatting change, or a wording-only docs group; leave it empty rather than fill it. Draft mirrors and mechanical groups stay empty unless a fetch proves otherwise.
Document lookFor holds claims about the whole change, and the divergences between the sources and the code at head. Source items are each requirement and acceptance item in a ticket, each behavior claim in the pull request description and commit messages, and each request in a review comment. Test plans, manual QA notes, and out-of-scope lists are context, not items. Read the truncated source for each item and give it a verdict: done as asked, done differently, not done, or not true at head. Each verdict other than done-as-asked is one bullet: what the source asks or says, what the digest or fetched hunk shows, and the reason when a source or the code gives one, or that no source gives one. When a later source changes what an earlier one asked (a review comment overrides the ticket), the ticket item is done differently, and the bullet names both sources. A manual step a source says the change needs (turn on a setting, run a migration) is also a bullet. Cite with [text](source:s1); the id goes in sources, and validate fails on an unknown id. Omit the field when nothing needs a bullet.
Write the prose
Document title, document why, group why, summary, and lookFor are for a tired engineer on the first read.
- One thought per sentence; split past about 25 words.
- Present tense. Name who does what.
- Use the real symbol, path, flag, or command.
- Cut every word that does no work. No changelog voice, no puffery, no "not just X, but Y".
- A straightforward group needs only its title,
why, andsummary; the live diff does the rest.