Repository navigation
Releases: context-labs/loupe
Release list
v0.13.0 — review several directories as one system
dir takes a list
Systems that span folders can be reviewed as one. The monorepo's inference/ platform routes inferences through elixir_engine/; a PR often changes both, and a reviewer that sees only one side misses the contract between them.
- Only changed files under a listed directory are in scope.
- Convention docs (
AGENTS.md,CLAUDE.md, …) are read from each directory. - The harness runs at the repo root so the agent reads both sides of a change in one review; the on-disk diff and the file tree carry both.
- Per-reviewer
dir(string or list) overrides the top level, so one reviewer can cover the engine alone while another covers both. - The Action input
dirand the CLI--diraccept a comma-separated list.
Not a breaking change. dir: "inference" validates and behaves exactly as before: cwd inside the directory, the path-mapping note, prefixed convention paths. Existing configs need no edits.
Until now the only way to span folders was to drop dir, run from the repo root, and hand-write include globs and explicit convention paths.
Also in this line since v0.12.0
- v0.12.1: a clean review may omit the
findingsarray (was falling back to headless). - v0.12.2:
@loupe reviewedits its ack into a per-reviewer completion line. - v0.12.3: call-site discovery understands Elixir (
def/defmacro,.ex/.exs,mix.exsas package root).
v0.12.3 — Elixir call sites
Call-site discovery understands Elixir
Consumers that review several languages in one config (the monorepo now reviews inference/ TypeScript and elixir_engine/ Elixir together, since the platform routes inferences through the engine) got no "Call sites of changed exports" section for Elixir changes. Discovery only knew export function declarations, .ts/.js sources, and package.json as the package boundary.
defanddefmacronames count as changed exports.defpis private and skipped..exand.exsfiles are searched.mix.exs,go.mod,Cargo.toml, andpyproject.tomlmark a package root alongsidepackage.json.
A def resolve/1 → resolve/2 change in lib/router.ex now lists its caller in lib/handler.ex, scoped to the Mix project.
v0.12.2 — @loupe review reports completion
@loupe review now tells you when it is done
Each reviewer keeps one summary comment per PR and updates it in place. That is what makes incremental review work, but it made a forced re-review invisible: @loupe review posted "🔍 On it", the summaries higher up the thread changed silently, and nothing new appeared. On a PR outside the config's dir, every reviewer found zero files, posted nothing, and the ack stayed forever.
The ack is now edited into a completion line when the run finishes:
✅ Re-review of `c61996e` done.
- code: ✅ no issues
- zdr: 🟡 1
- docs: ✅ no issues
Each reviewer's summary comment above was updated in place.
or, when no changed file is under the config's dir:
Nothing to review: this loupe config covers `inference/` and no changed file is under it.
A reviewer that failed shows ⚠️ failed (see the failure comment).
Internals
runReviewsreturns one outcome per reviewer instead of a boolean.postIssueCommentreturns the comment id; newupdateIssueComment.- Docs:
docs/how-it-works/chat.md.
v0.12.1 — accept clean reviews without a findings array
Fix
Clean reviews that omit findings no longer trigger the headless fallback. v0.12.0 tightened the parser so {} or {"status":"done"} could not pass as an empty clean review. The check also required a findings array, and glm-5.3 routinely answers a clean diff with {"summary": "… No defects found."} and nothing else. That valid answer was rejected, the agentic result discarded, and the review re-run headless with a ⚠️ degraded run marker. Three production reviews on the monorepo hit this in the first day.
Now a review needs a string summary. findings and concerns may be omitted and must be arrays when present. {}, {"status":"done"}, and {"summary":"s","findings":"none"} still reject.
First-day report for v0.12.0 on the monorepo
From 67 summaries posted after v0 moved:
- 64 agentic, 3 headless fallbacks, all three with the cause above. No other fallback cause observed.
- 29 incremental (delta) runs, 38 full. No unknown-history runs.
- Thread resolution confirmed: PR 6413 has three loupe threads resolved across three pushes, PR 6416 one. History intact.
- Verification: 2 passed, 1 invalid (the model answered in prose; all findings kept, as designed), the rest skipped because there was nothing to verify.
- Summaries state which callers were checked in 40 of 67 runs, which is the review procedure taking effect.
- Findings posted were substantive: fail-open retention defaults for zero-retention teams, an
ACCESS EXCLUSIVElock note on a trigger drop, an unpinned-deployment resolution change. No blockers, no false positives reported.
v0.12.0 — find the caller, keep the history, show the run
Why this release
A bug that Cursor Bugbot caught on context-labs/monorepo#6377 and loupe missed. Replaying that PR's pre-fix commit with the consumer's real config (glm-5.3, maxTurns: 5, low reasoning) showed loupe stopping one file short of the bug on every run. This release is the set of changes that makes the same replay find it, plus the customer-requested comment-history change and the observability every run was missing. Tracked as INF-5289 (INF-5290 through INF-5295).
Replay scorecard, same PR, same config, no hint in the PR text:
| loupe | Runs | Found the spinner-over-prompt bug |
|---|---|---|
| v0.11.1 | 1 | no |
| v0.12.0 | 2 | yes, 2 of 2, as a blocker that survived verification |
What changed for reviews
Callers of changed code are found for the agent, not by the agent. Before the harness starts, loupe finds every exported value the diff touches (declaration lines, hunk headers, and hunk context), greps its callers within the same package, follows the enclosing function of each call up to three hops, and injects the list into the prompt with four lines of preceding context per site. Test files, strings, comments, and vendored directories are skipped. A five-turn agent now sees withProgress( wrapping ensureSession() wrapping login() without spending a single tool call to get there.
An always-on review procedure. Custom reviewer prompts replace loupe's default guidance, so procedural rules never reached them. A short procedure block is now appended regardless: read the listed callers before judging, follow thin wrappers one more hop, and treat a newly interactive or blocking call inside a spinner or other terminal-owning wrapper as a defect rather than a cosmetic note. Turn it off with procedure: false at the top level or per reviewer.
Incremental runs see the whole PR. Every in-scope patch is written to the on-disk diff on every run. On an incremental run the prompt names the files to reassess and labels the rest as context. Findings anchored on context files are dropped and counted, so they cannot duplicate a prior thread that was deliberately kept. A history lookup that fails, a compare that errors, or a compare at GitHub's 300-file cap means "unknown": full review, nothing cleaned up.
Reasoning effort reaches the harness. reasoning is passed natively: whip defaultEffort in the materialized WHIP_HOME, claude --effort, codex -c model_reasoning_effort. It also applies to the verify pass, chat, and fix. Unset means the harness's own default and no reasoning sentence in the prompt. Known limit: whip without a whip config block keeps its own default.
Verification fails open on evidence outside the diff. The verifier may reject a finding only when the diff itself contradicts it or it duplicates another. A finding grounded in a checkout file is no longer thrown away as "code not shown". Verification output must carry exactly one verdict per finding, otherwise it is ignored and every finding is kept.
Markdown bodies. The output contract and default guidance no longer cap findings at one or two sentences. Summaries, concern details, and finding bodies are GitHub Markdown with paragraphs and fenced code. Off-diff notes and concerns render as blocks, so fences survive.
What changed on the PR
Prior inline comments are resolved, not deleted. New priorComments setting, top level, per reviewer, Action input prior-comments, CLI --prior-comments:
| Policy | Effect |
|---|---|
resolve (default) |
The thread is resolved via GraphQL. History stays visible under "Show resolved". Means "superseded by a newer review", not "proven fixed". |
delete |
Previous behavior. |
keep |
Nothing is touched. New comments accumulate. Useful for side-by-side evaluations. |
Only threads rooted by loupe's own login with this reviewer's marker qualify, scoped to the reassessed files on an incremental run and to in-scope files on a full run. Threads the token cannot resolve are left open with a warning. Cleanup now runs after the new review and summary post, from a snapshot taken before, so a failed post never leaves a PR with old comments gone and nothing in their place.
Run details on every summary. A collapsed block reports the review mode (agentic, headless, or headless fallback after an agentic failure), verification status (passed, skipped, invalid, failed), scope (full, delta, unknown), and counts of findings dropped as malformed, out of scope, below the noise profile, or rejected by verification. The stat line shows ⚠️ degraded run when any of that lost something.
Failures are visible. A reviewer that throws posts a ⚠️ loupe · <name> could not complete this review comment (no marker, no SHA, so it never advances incremental state) and the job exits 1. The other reviewers keep running. The parser now rejects objects that are not a review ({}, {"status":"done"}) instead of treating them as a clean pass, and an agentic run whose output is not a review gets the same single headless retry a crashed run gets.
Path mapping under dir. When the harness runs inside a subdirectory, the prompt says so and explains that listed paths are repository-relative. This alone had been costing consumers a wasted turn per run.
Behavior changes for @main and @v0 consumers
- Prior inline threads are resolved on re-review. Set
priorComments: "delete"to keep the old behavior. reasoningunset no longer injects "Reasoning effort: low" into the prompt.- Every summary gains a
Run detailsblock. Custom reviewer prompts gain the procedure block unlessprocedure: false. maxTurnsdefault is unchanged at 10. Consumers running at 5 should expect the call-site list to do more of the work, not more turns.
Docs
- New
docs/how-it-works/: triggers, review run, what the agent sees, GitHub objects, first vs incremental runs, chat. Mermaid diagrams throughout. docs/configuration.md,docs/guide.md,docs/github-action.md, andREADME.mdupdated for every new field, flag, and default.- loupe reviews its own PRs with a new docs-currency reviewer (
.loupe/reviewers/docs.md).
Tests
122 tests. New end-to-end runReview coverage with an injected Octokit and a scripted harness: incremental scope and cleanup ordering, unknown history, headless fallback, the compare cap, dry run, and the call-site section. Call-site discovery has its own unit tests for hunk parsing, package scoping, string and comment filtering, and the three-hop chain.
Review process for this release
Plan and tickets critiqued by gpt-6-astra before coding. Adversarial pass on the diff: 3 raised, 1 fixed, 1 deferred and documented, 1 refuted. loupe's own code and docs reviewers ran on every push and found four real items that were fixed before merge.
v0.11.1
What's Changed
- chore: track the latest whip release by @atbe in #8
- fix(harness): tolerate a whip that predates -cache-key by @atbe in #9
Full Changelog: v0.11.0...v0.11.1
v0.11.0
What's Changed
Full Changelog: v0.10.2...v0.11.0
v0.10.2
What's Changed
- perf: run reviewers concurrently instead of sequentially by @francescov1 in #5
- ci: real CI + loupe self-review; docs: release/versioning by @atbe in #6
New Contributors
- @francescov1 made their first contribution in #5
Full Changelog: v0.10.1...v0.10.2
{ "dir": ["inference", "elixir_engine"] }