Guard · Diff review
Review the lines a change adds
RepoOps diffs a repository's working tree against its tracked branch, or any base..head range, and runs four passes over only the lines the diff adds: the repository's own decision guards, the security and env detectors, a quality pass, and blast radius from the code graph. It makes no model call, and a clean result says which guards ran.
For: the engineer who reviews an agent's change before it becomes a pull request, and the maintainer who declares the repository's decision guards
What it does, and why it helps
The Review queue page, opened from AI Security under All tools, reads GET /api/review. The route runs git diff with three lines of context against the tracked remote and branch (origin/main by default) or the base and head refs you type, bounded to five seconds and 64 MB, and hands the text to the pure detector in lib/review/diff-review.mjs. The detector parses the unified diff and keeps only the added lines. Pass one matches them against the guards declared in the repository's .claude/brain/decisions.md. Pass two rebuilds the added lines as a sparse file and runs the same security and env detectors the Security and Config tabs use, keeping only a finding whose line this diff added. Pass three is a quality pass over code files: a new function that runs 60 or more added lines, four or more identical consecutive lines, a line added three or more times, a statement added directly after a return, throw, break or continue at the same indent, and a new I/O call with no await, try, catch or fallback visible on the same or the prior added line. Pass four reads the code graph and marks a changed file that ten or more nodes depend on. Findings are deduplicated, ranked high, medium, low, info, and grouped by file on the tab.
The route has one deliberate side effect. When an active lesson's trigger text contains a finding's rationale, or the rationale contains the trigger, that lesson's prevented_count goes up by one and a row lands in the repository brain's prevention-events.jsonl, once per lesson, file, line and kind. That is the count the prevented-recurrence figures read. A write failure there never changes the response. The same detector runs as a CI gate through npm run review-check, which exits non-zero when a finding matches an active lesson, and as a TypeScript port inside the GitHub App, which posts a review comment and a RepoOps review check-run on a pull request once the App is installed on the repository.
The pain. An agent changes forty lines across six files. The whole-tree scanners report everything wrong with the repository, most of it old, and the reviewer cannot tell which finding this change introduced.
The point of view. Judge the change, not the repository. Scope every check to the lines the diff adds, name the pass and the reason for each finding, and when nothing is found, say which rules ran so a clean result is a claim you can check.
What gets easier. Reading. The tab groups findings by file with a severity chip, the line, the kind and one sentence of reason, and closes with a line that says how many guards from decisions.md ran, or why none did.
When it helps. Before an agent's uncommitted change becomes a pull request, or over any base..head range you can name, on a repository whose tracked branch has been fetched. In CI, on every pull request, through review-check.
Its limits. It flags patterns, not intent. Only added lines are checked, so a defect that was already in the file stays invisible here; the Security and Config tabs cover the whole tree. Pass one checks nothing until a decision declares a guard. The blast-radius pass needs the code graph and skips a file that is not a node in it. On the desktop it merges, blocks and edits nothing; the CI gate and the hosted check-run are the only places it can hold a merge.
Understand it in 30 seconds
Read the narration
- 0:00 An agent wrote the change.
- 0:02 Which of its added lines carry new risk?
- 0:06 RepoOps reviews only the lines this diff adds,
- 0:09 and it makes no model call.
- 0:13 Each finding names the file, the line, the pass and its reason.
- 0:17 A clean result still says which brain guards ran.
- 0:23 Decide on the line, not the whole repository.
- 0:26 The guide covers guards.
Synthetic example. Read the guide
Where to find it
Where to find it
- Desktop:
localhost:4000, then AI Security in the sidebar, then Review queue under All tools, in the Vulnerabilities group. - Hosted:
repoops.ai/team/review, from AI Security in the sidebar, then Review queue under All tools, in the Vulnerabilities group. - Keyboard: ⌘ K, then type “Review queue”.
When to use it
An agent's uncommitted change, before the pull request
Situation. Claude Code added a route handler and a helper in one session. The repository tracks origin/main and the ref is fetched.
What you do. On AI Security, open Review queue under All tools. The tab reviews the working tree against origin/main on load; press Review diff to run it fresh.
What you see. A row of severity pills, for example 1 medium and 2 low. Under the file lib/routes/orders.mjs a row reads medium, :41, quality:unreachable-code, and the reason. Another reads low, :58, quality:unguarded-io. The closing line reads Brain decision rules: 2 guards from .claude/brain/decisions.md ran against this diff.
What it establishes. Which added lines to read, and why, before anyone else sees the change. Nothing about the diff itself is stored.
A repository rule you want checked on every diff
Situation. A decision in decisions.md says the checkout routes never read process.env directly. Nobody re-reads decisions.md while reviewing.
What you do. Under that decision's dated heading add one line: **Guard.** `lib/routes/checkout*.mjs` must not match `process\.env` (severity: high). Press Review diff.
What you see. A high row reads brain-rule, with the decision's title as the reason and (cite: .claude/brain/decisions.md:112). The closing line counts the guard. A guard whose pattern is over 200 characters or nested-quantified is listed as skipped with its reason, never dropped in silence.
What it establishes. The decision is checked against every later diff of that path, on the tab and through review-check in CI. It is a pattern match on added lines, not proof the decision is honoured elsewhere.
Before you start
- Supported versions
- RepoOps desktop v0.3.1, the release this guide was read against. Git on the machine: the route runs git diff in the repository's own checkout.
- Where it runs
- Local: AI Security, then Review queue under All tools, and GET /api/review with repo, base and head query parameters. CI: npm run review-check in the repository. Hosted: repoops.ai/team/diff-review, under All tools on AI Security, is a desktop-only stub, because the working tree never leaves the machine. The Review queue at repoops.ai/team/review is a different surface, the team's queue of pull requests awaiting a person, which receives the findings rollup from the GitHub App. The App runs the ported detector on a pull request and posts a review comment plus a RepoOps review check-run; its port runs the security, env and quality passes only, without brain rules or blast radius.
- Permissions
- None beyond the local app. The route reads the repository checkout and its brain, and writes only the prevention state and ledger named under Data. The GitHub App has to be installed on the repository by someone who can install Apps there.
- Connections
- The base ref fetched locally (open the repository's sync first when a ref is unknown). No API key: no pass makes a model call. Optional: REPOOPS_AWARENESS_WEBHOOK_URL, for the team chat event a match against a teammate's shared lesson fires.
- Plan
- No plan gate: the pricing capability map in website/lib/pricing-tiers.ts has no review row. The hosted team Review queue follows the hosted dashboard tiers.
Configure it
- Open the tab and press Review diff.
On open the tab paints the last default review from its browser cache, then fetches a fresh one. Review diff always fetches fresh. With both ref fields empty the range is the tracked remote and branch to the working tree; the placeholders read origin/main and (working tree).
- Type a base ref, or a base and a head, for a range.
base ref alone reviews the working tree against that ref. base ref with head ref (optional) reviews base..head, which is how you review committed work such as a branch. Enter in either field runs the review. An unknown ref returns the amber banner, not a crash.
- Declare a guard on a decision.
In .claude/brain/decisions.md, inside a ## YYYY-MM-DD heading, one line of the form **Guard.** `path glob` must not match `regex` (severity: high). Both spans backticked; the severity suffix is optional and reads medium when absent; ** crosses directories and * does not. A guard outside a heading, over 200 characters, uncompilable, nested-quantified or with an unknown severity is skipped, and the tab names it and the reason.
- Run the same review as a CI gate.
npm run review-check diffs the pull request against origin/main (or GITHUB_BASE_REF), runs the detector, matches each finding against the repository's active lessons with the same scorer, and exits 1 on a match. The strict flag also fails on any high finding. It is separate from ship-check; require either or both as a branch-protection check.
- Optionally, install the GitHub App for pull requests.
The hosted webhook runs the ported passes on the pull request diff and posts a comment and a neutral RepoOps review check-run. GITHUB_REVIEW_MERGE_BLOCK set to 1 on the website turns a high finding into a failing check. Brain rules and blast radius stay on the desktop.
| Setting | Where | A sensible choice | Why it matters |
|---|---|---|---|
base ref | Review queue tab, the first field | empty, which reads as the tracked remote and branch | The diff is what changed since this ref; a ref that is not fetched locally fails the review with the amber banner. |
head ref (optional) | Review queue tab, the second field | empty for the working tree, or a ref for a base..head range | Set it to review committed work rather than the uncommitted tree. |
**Guard.** line | .claude/brain/decisions.md, under a decision's dated heading | one guard for a decision that is a pattern, none for a decision that is judgement | Pass one checks nothing without one; the tab's closing line says which of its three states pass one was in. |
(severity: high) | the optional suffix on a Guard line | medium, the default, unless the decision names a real defect class | One of info, low, medium or high; any other value skips the guard and the tab says so. |
--strict | the review-check command line | off, unless a high finding should fail the gate on its own | By default only a finding an active lesson already covers fails the gate. |
REPOOPS_REVIEW_CHECK_OFF | the environment review-check runs in | unset | Exactly 1 makes the gate exit 0 and print that it skipped; the gate's own help calls the override not recommended. |
GITHUB_REVIEW_MERGE_BLOCK | the website's environment, for the GitHub App | unset, so the check-run stays neutral | Exactly 1 turns a high finding into a failure conclusion on the RepoOps review check-run. |
REPOOPS_AWARENESS_WEBHOOK_URL | the data directory's .env | unset, unless a team chat should hear about a prevented recurrence | With it set, a finding a teammate's shared lesson covers fires a prevented-recurrence event carrying file, line, kind and a link, under the awareness interruption budget. |
What you should see
A diff with findings
Configuration. A working tree with added code, both ref fields empty, two guards declared in decisions.md.
Expect. Severity pills for the counts present, then one card per changed file. Each row shows a severity chip, the line as :41, the kind (brain-rule, security:open-cors, env:prod-unsafe, quality:large-function, blast-radius and the rest) and one sentence of reason. The closing line names how many guards ran.
Verify. Every row's line number is a line this diff added. The same range through GET /api/review returns the same findings array plus commentBody, the markdown the GitHub App would post.
A clean diff
Configuration. Any range whose added lines match nothing.
Expect. A green banner, No findings across N changed files, and under it the brain-rules line in one of three states: this repo has no .claude/brain/decisions.md so pass 1 checked nothing; none declared, with the instruction to add a **Guard.** line; or N guards from .claude/brain/decisions.md ran against this diff.
Verify. A clean banner with no rules line under it is an older cached response; press Review diff and the line appears.
A range that cannot be diffed
Configuration. A base ref that is not fetched, or a typo in either field.
Expect. An amber banner: Couldn't review: git diff failed for base..head (unknown ref?), with the instruction to open the repo's sync first so the base ref is fetched locally. The route answers ok false with an empty findings list and zero counts, and writes nothing.
Verify. prevention-state.json in the repository brain does not change. Fetch the ref and run the same range again.
Data and cost
- What is captured
- Nothing about the diff is stored by the route. Three things are written, only when a finding matches a lesson: the lesson row in the repository brain's lessons/lessons.jsonl (prevented_count, and last_match_defect holding the file, line and rationale), a row in prevention-events.jsonl, and the seen key in prevention-state.json. A match against a teammate's shared lesson writes org-prevention-events.jsonl and org-prevention-state.json in the aggregator's own brain root instead. The browser keeps the last default-view response in the repoops-tabcache IndexedDB store, or localStorage when IndexedDB is unavailable.
- Who can see it
- Local. The desktop route sends nothing to repoops.ai, which is why the hosted diff-review page is a stub. On the hosted path the GitHub App posts the comment body and the check-run to the pull request on GitHub and, when the installation maps to a team, upserts the pull request with its findings summary into the team Review queue.
- How long it is kept
- The lesson row, the two ledgers and the state files are kept; no route prunes them and no retention knob exists. The browser entry is replaced by the next default review and goes with the browser's site data.
- What leaves the machine
- None from a desktop review. One optional event: with REPOOPS_AWARENESS_WEBHOOK_URL set, a prevented recurrence against a teammate's shared lesson posts the finding's file, line, kind and a localhost link to that webhook. The hosted App talks to api.github.com with its installation token.
- What it costs
- No model call on any path. The git diff is bounded to a five-second timeout and a 64 MB buffer. The code graph is derived once per synced SHA and served from memory or the disk artifact cache. The route's result is never cached on the server, because the diff changes on every edit and the prevention write has to run.
When the result differs
| Symptom | Likely cause | Next action |
|---|---|---|
| Couldn't review: git diff failed for origin/main..the working tree (unknown ref?). | The base ref is not fetched locally, or a ref name is wrong. | Open the repo's sync first, or type a ref that exists, then press Review diff. |
| No findings, and the last line says this repo has no .claude/brain/decisions.md, so pass 1 checked nothing. | The repository has no brain decisions file. | Read the clean result as the other three passes only, or add decisions.md with a decision and a Guard line. |
| Brain decision rules: none declared. | decisions.md exists but no decision carries a Guard line. | Add a **Guard.** line under the decision heading, as the line itself says. |
| N declared guards were skipped as unusable. | A guard sits outside a dated heading, its pattern is empty, over 200 characters, uncompilable or nested-quantified, or its severity is not info, low, medium or high. | The line names each skipped guard by decisions.md line and reason; fix that line. |
| A secret or an open CORS header plainly in the file is not flagged. | It was already there. Pass two keeps only a finding on a line this diff added. | Run the Security or Config tab for the whole tree. |
| No blast-radius row for a changed file. | The file is not a node in the code graph, nothing depends on it, or the graph read failed and the pass degraded. | Open Blast radius for that file; it reads the same graph and says whether the node is known. |
| review-check exits 1 in CI. | A finding's rationale matches an active lesson's trigger, or the strict flag saw a high finding. | Read the printed lesson and fix the line. REPOOPS_REVIEW_CHECK_OFF=1 skips the gate, and the gate prints that it did. |
| repoops.ai/team/diff-review says the tab is desktop only. | By design: the working tree never leaves the machine. | Use the desktop tab; for pull requests, the GitHub App comment and check-run. |
- Disable
- Nothing to switch off on the desktop; close the tab. For CI, remove the review-check step or set REPOOPS_REVIEW_CHECK_OFF=1. For the hosted comment and check-run, uninstall the GitHub App from the repository at GitHub.
- Roll back
- Not provided. A prevented_count bump and its ledger row are not undone by the tab or by any route; the data sits in the repository brain's lessons/lessons.jsonl, prevention-events.jsonl and prevention-state.json, and for team lessons in the aggregator brain's org-prevention-events.jsonl and org-prevention-state.json. A review changes no source file, so there is nothing else to roll back.
- Revoke access
- Nothing to revoke locally; the route needs no credential. The GitHub App's installation token is minted per event from the App's key, and uninstalling the App at GitHub ends it.
- Delete
- Not provided. No route deletes a prevention row or a seen key; the files are named under Roll back. The browser cache entry goes with the browser's site data for localhost:4000.
Related tasks
Maintenance evidence
- Feature id
diff-review(spine leafreview-queue)- Owner
- Homepage reality closure, Wave 2 (R2) and Wave 4 (R1a, R1b); the G113 brain-rule fix in lib/review/decision-rules.mjs. Guide: LDG-0717.
- Supported product version
- RepoOps v0.3.1
- Last verified
- 2026-09-15, read against origin/main at 52366bb6d; labels read from the served tab source (public/review.html), the route (lib/routes/review.mjs), the detector (lib/review/diff-review.mjs), the CI gate (bin/review-check.mjs) and the hosted pipeline (website/lib/github/pr-review.ts). No live instance was run for this guide.
- Example fixtures
- No fixture file; inline diffs in lib/review/diff-review.test.mjs (26 cases: the parser, the scoping, the quality heuristics, ranking), lib/review/prevention-recorder.test.mjs (11: the match, the dedupe, sibling repos), lib/review/comment-renderer.test.mjs (22), test/review-decision-rules.test.mjs (the Guard line parser) and lib/proof/pr-review-projector.test.mjs (the glob and the line counter).
- Source references
lib/routes/review.mjs,lib/review/diff-review.mjs,lib/review/decision-rules.mjs,lib/review/prevention-recorder.mjs,lib/review/comment-renderer.mjs,lib/proof/pr-review-projector.mjs,lib/security-guardrails.mjs,lib/env-doctor.mjs,lib/lessons.mjs,lib/triage-ranker.mjs,lib/org-prevention.mjs,bin/review-check.mjs,public/review.html,public/lib/repoops-tab.js,website/lib/github/pr-review.ts,website/lib/review/diff-review.ts- Documentation review
- Independent review requested on the slice pull request; not yet recorded.
- Video review
- Narrated story rendered and published 2026-09-26 (render 322a0038dc08, LDG-1014) with the breadcrumb AI Security, which lists the feature under Moved here, checked against main at 7aab4cd82 with LDG-1014 part 1. Six frames, the captions and the transcript were reviewed by the authoring agent, not an independent reviewer; the audio was not listened to by a person. Narration is the provisional Windows voice until LDG-0721.
Last updated