Kaniscope
🦀 engine pr-review-core v0.31.0 · MIT / Apache-2.0

Code review that lands on the line

Kaniscope reads a pull request, finds what is actually wrong, and posts inline comments anchored to the exact lines — with fixes you can commit in one click. One binary, no runtime.

Try it on a real PR → Install
# the CLI on your PATH
npm install -g kaniscope
kaniscope --provider github --repo me/app --pr 12

What a review looks like

Not a wall of prose on the PR. A comment on the line, with the fix attached.

src/session.rs +3 −1
pub fn expire(&mut self, now: Instant) {
- self.entries.retain(|e| e.at > now);
+ let cutoff = now - self.ttl;
+ self.entries.retain(|e| e.at > cutoff);
+ self.last_swept = now;
}
src/session.rs:41
⚠ HIGH

now - self.ttl panics on overflow when ttl exceeds the process uptime, which is every session on a freshly restarted node. Fix: use checked_sub and treat None as “nothing expired yet”.

Suggested change · commit it from the PR
let cutoff = now.checked_sub(self.ttl);

One call does all of this

The parts that take months to get right are the parts you do not write.

fetch the diffglob filterrank & pack to budget tree-sitter contextcomplexityblast radius CVE scanreviewself-critique confidence flooranchor to a real linere-anchor on drift suggestionspost & reconcile

More than the diff

A reviewer that only ever answers one question is a linter with a bigger bill. These are the other things it already does, in the same binary.

💬

Ask it things, in the PR

/ask answers a question about the change from its diff. /describe writes the PR description and rewrites it idempotently, keeping your edits. /review-file <path> reviews a whole file at the head, not just the lines you touched.

⚙️

Rules that live in the repo

A .prbot.toml at the root sets the model, the globs, the confidence floor and the per-PR cap — and takes free-text instructions, so “we never use unwrap() outside tests” is a line in the repo rather than a prompt someone owns.

🗺️

A walkthrough you can check

A per-file table — lines changed, definitions touched, worst complexity grade, findings filed — and a mermaid diagram of how the changed symbols call each other. Both are derived from the tree-sitter parse, never written by the model, so every row and arrow is checkable against the code.

🛡️

CVEs in what you just added

New lockfile pins — Cargo, npm, yarn, pnpm, Go, PyPI, RubyGems, Composer — go to OSV.dev, and known advisories land in the summary with severity and fix version. No resolver to install, and it fails open: a scan that cannot run never costs you the review.

Two lines in a workflow

The shortest way to try this on a repo you already have. It runs on your Actions minutes with your key, and posts as the token you give it.

# .github/workflows/review.yml
name: review
on:
  pull_request:
permissions:
  contents: read
  pull-requests: write     # it posts the review as comments
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: nhatvu148/kaniscope-action@v1
        with:
          openrouter-api-key: ${{ secrets.OPENROUTER_API_KEY }}

On the GitHub Marketplace. For GitLab and Bitbucket the same binary runs in CI the same way — it is a process that takes a repo and a PR number.

What it costs

There is no seat. You pay your model provider for the tokens a review spends, and nothing to us — there is no us in the path. So here is the bill, measured rather than estimated.

$1.05
median cost of one review
$3.62 at the 90th percentile
169
reviews behind that number
19 repositories, 31 Aug – 8 Sep 2026
$0
per developer, per month
a quiet repo costs what it reviews

How that is calculated, so you can disagree with it. Those runs used the agentic path — it clones the repo and lets the model grep and read around the diff — on claude-sonnet-5, and the median one spent 477,545 input and 9,333 output tokens. Priced at that model’s list rate of $2 / $10 per million tokens, the median comes to $1.05. That is the expensive end on purpose: the diff-only path costs a fraction of it, and pointed at a small local model through Ollama the marginal cost is your electricity. Your own numbers will differ with your model, your diffs and your provider’s pricing — the point is the shape of the bill, not the digit. It scales with pull requests, not with headcount, and nobody meters you per file.

Measured, not claimed

Every reviewer says it finds real bugs. We keep a scoreboard of what ours actually filed, including the times it was wrong, because a precision number without a false-positive column is marketing.

81%
precision over 37 reviewed rounds
59 confirmed findings, 14 false positives
4
of those false positives were BLOCKING
the failure that costs trust, so we count it
?
recall
we do not have an honest number yet, and say so

A human read every one of those reviews and wrote the verdict down. The full scoreboard, the method, and what we still cannot measure →

Build your own bot

The engine is a binary, so a bot in any language is one spawn and one JSON document. Node and Python get a typed client over it.

import { review } from "kaniscope";

app.post("/webhook", async (req, res) => {
  if (!verifySignature(req)) return res.sendStatus(401);
  res.sendStatus(202);                          // a review takes minutes

  const out = await review({ provider: "github", repo, pr });
  console.log(out.recommendation, out.findings);   // already posted
});

Result types are generated from the binary’s own --schema, so a client can never drift from what the engine emits. There is a complete worked bot with tests that need no API key.

Why it is a binary

📦

No runtime to install

One static binary. The Python wheel is py3-none — it carries the binary, not an extension, so it works on any Python without a compiler.

🎯

Comments that anchor

Findings land on lines the provider will actually accept. A finding that drifts a line or two is re-anchored rather than dumped into the summary.

🔁

Re-reviews reconcile

A second review updates its own comments and resolves what you fixed, instead of stacking a duplicate set on the PR.

🌍

GitHub, GitLab, Bitbucket

The same engine and the same output on all three, each verified on live pull requests.

🔒

Your key, your box

Self-hosted by default. Point it at OpenRouter, or at Ollama, vLLM or anything OpenAI-compatible — including fully offline. Your diff goes to the endpoint you configured and nowhere else; there is no server of ours between you and it.

Advisory, always

It comments. It never blocks a merge, never pushes a commit, never approves on your behalf. A suggestion becomes a commit when you press the button on the PR.

🧩

A library too

pr-review-core on crates.io if you want the engine in your own Rust program, with a pluggable review backend.

📐

Big PRs stay reviewable

Over the token budget, whole files are ranked (source before tests before docs) and packed, instead of the diff being cut off mid-hunk. A file and its test travel together, and whatever did not fit is named to the model.

🔇

Built to say less

A self-critique pass drops its own false positives before anything posts, a confidence floor cuts the rest, and a per-PR cap stops it burying a real finding under nits.