lenscheck-semantic-reviewer

See what a pull request actually changes — no LLM required

Git tells you what lines changed. Lenscheck tells you what they mean — and points you at the exact line to read.

A deterministic semantic PR reviewer for Python web apps. No AI black box, no API key, no per-PR token bill — same PR in, same review out, every time.

No LLM No API key $0 per PR Deterministic Runs offline
reviewer pip install lenscheck-semantic-reviewer
contract pip install lenscheck-contract
★ Star on GitHub · · by Ankush Singh Gandhi · warriorwhocodes.com
The ecosystem

Two tools, one promise: always know what your backend does

Lenscheck looks at your backend from both sides — the diff from the outside and the running app from the inside. Each catches what the other structurally can't.

🔎

lenscheck-semantic-reviewer

Outside-in. A GitHub Action / CLI that reads the PR diff (AST, no LLM) and comments on what actually changed and where to look. Lives in your CI — zero code, zero setup.

pip install lenscheck-semantic-reviewer · PyPI ↗

🛡️

lenscheck-contract

Inside-out. A library inside your Django app that records what it really does — routes, models, effects — fails CI on a risky change, and blocks undeclared calls at runtime.

pip install lenscheck-contract · PyPI ↗

◭ the bridge — runtime truth feeds the reviewer
$ lenscheck-contract invariants -o invariants.json        # the app's real declared egress + rules
$ lenscheck review <repo> --pr 128 --invariants invariants.json   # the reviewer stops guessing

On its own the reviewer guesses your invariants from git history. lenscheck-contract hands it the truth instead: your app's declared destinations become an enforced allowlist, so a brand-new domain sneaking into a PR is a 🔴 critical alert — grounded in fact, not a hunch. See the full docs →

lenscheck-contract · inside-out

Your backend can't lie about what it does

Static analysis can't see routers, loops, or mixins — but Django already resolved all of it at startup. So lenscheck-contract asks the running app instead of parsing files.

1 · Zero code

lenscheck-contract export

Reads Django's real router + model registry → contract.json. Coverage reported, not hidden.

2 · Declare

a decorator, done

Mark the routes + effects that matter — a decorator that looks like every Django one.

3 · Enforce

the runtime guard

One hook at the socket layer: an undeclared call fails loudly. The bad code doesn't ship.

3 bad things in. 0 shipped.

A PR that "looks harmless" — caught on two layers: structural (the CI diff) and runtime (the guard).

lenscheck-contract catching three bad things across CI and runtime
  • Layer 2 · CI diff — auth weakened (user → public) and a model field droppedCI blocked before merge.
  • Layer 3 · runtime guard — an undeclared exfiltration call blocked before it ever left the process.
  • Structural mistakes → this. Logic mistakes → your tests. You need both.
The problem

A single PR can be 60 files and 18,000 lines

A normal line-diff buries the one change that matters — "user email now leaves the server" — under thousands of mechanical edits. You don't need to read all of it. You need to know what changed in behavior, how it flows, where to look, and whether it broke a promise.

$ lenscheck review . --pr 481   →   🔴 3   🟠 4   🟡 4   🟢 20
# 17,841 lines changed → 3 things to actually look at

🔴 NEW ENDPOINT  /api/companions/payment
   why: new endpoint exposes a PII path with open/unspecified auth
   flow: /api/companions/payment → CompanionPayment → {Order:write} → ext: PayTM
   investigate: payment_views.py:90  email → requests.post @ services.py:9

🟠 CHANGED       /api/orders/invoice
   why: new external call: razorpay.post  ·  money path

🟢 REFACTOR      /api/users/profile
   handler renamed, semantics unchanged — nothing to see
The whole idea, in one line

Code → facts → diff → show what matters

No running your code. No guessing. Just Python's own parser (AST) reading structure — the same three boxes, small.

ANALYZER

code → behavior facts

DIFFER

facts → what changed

SURFACE

rank + point at the line

See it

What changed → how it flows → where to look

A ranked list, an interactive flow graph for every change, and the exact file:line locations — in the terminal, a web UI, a PR comment, or your Security tab.

A finished Lenscheck review in the web UI
Why Lenscheck

Why not just read the diff?

Because on an 18,000-line PR you can't — and the usual alternatives either guess, or miss what actually matters.

🎯

Facts, not vibes

It reads your code with Python's own parser and reports what's there — it never runs it, and never guesses. Every finding traces to a file:line, so there's nothing to fact-check.

🤫

Honest about the unknown

A tool that wrongly says "all clear" ships the bug. Lenscheck fails toward look here — ✓ / ⚠ / ? — and never renders "didn't see it" as "it's safe."

🔁

Refactor-proof

Facts are matched on the URL route, not class names. Rename every class in the repo and a pure refactor produces zero noise — so the real change stands out.

🔒

Your rules, enforced

It learns the promises your codebase already keeps and flags the PR that breaks one. That confirmed corpus is yours — the piece a competitor can't clone.

Read the diffAI reviewersStatic scannersLenscheck
Survives an 18k-line PR~~
No hallucinations
Honest about what it can't tell~
Refactor-stable (renames = no noise)
Learns your team's own rules
Zero config, zero dependencies~

It doesn't replace your tests or type-checker — it sees a different layer: behavior at the endpoint, and whether a change broke a promise.

The lens

Seven things it reads for every endpoint

It reads your code with Python's own parser — it never runs it — and reports facts, not guesses.

route → handlerwhich URL maps to which view?
authdoes it require a login / permission?
db tableswhich tables does it read and write? — resolved to the real SQL table name
external callsdoes it call another service (payment, email, …)?
asyncdoes it kick off a background job / thread / signal?
cachedoes it read or invalidate a cache key?
PIIdoes personal data travel toward something that leaves the app?
✓ verified — saw it clearly ⚠ potential — one hop away / behind config ? unknown — couldn't tell

It will never say "no PII leaves here" when it just didn't see it. Absence is shown as unknown, never as safe.

Run it your way

Terminal, a web UI, or on every PR

Same facts, three surfaces — pick whatever fits your workflow.

⌨️

CLI

lenscheck review — a ranked review right in your terminal or CI logs.

🖥️

Web UI

lenscheck serve — an interactive flow graph and where-to-look, in your browser.

🤖

GitHub Action

A sticky comment, inline notes, and an optional merge gate on every pull request.

The Lenscheck web UI
The moat

It learns the rules your code already follows

A property your codebase kept for 800 commits was almost certainly on purpose. Lenscheck mines your git history, surfaces those rules, you confirm the real ones — and then every PR that breaks one gets flagged, with the exact commit that broke it.

Lenscheck invariants: discover, confirm, and blame rules
More

Built to be trusted

🩸

Traced PII leaks

The exact field → the exact line where it leaves. Traced, not guessed.

📦

Dependency powers

Flags a bumped package that newly gains network / subprocess / native code.

🎭

Intent vs. behavior

PR says "just a refactor" but added an endpoint? Caught — from facts, not an LLM.

💬

Inline PR comments

Each finding pinned to the exact changed line, plus a sticky summary and a triage label.

🛡️

SARIF + gating

Findings in the Security tab, and an optional merge gate on a broken rule.

📊

Org roll-up

lenscheck digest counts the open risky PRs across every repo — one number for leadership.

Questions

The honest answers

Won't it be noisy?

No — it's a diff. It only speaks up about what a PR actually changed, and it labels anything uncertain "look here," never "safe." Roll it out in observe mode and you'll see the signal before it can ever block a merge.

Does it need setup or config?

Nothing. It's a pure-Python-standard-library package with zero third-party dependencies. Point it at a repo (local path or GitHub URL) and go — no config file, no server, no account.

Will it hallucinate like an AI reviewer?

No. There's no LLM in the analysis — so there's nothing to hallucinate, no API key to wire up, and no per-PR token bill. It reads your code with Python's parser and reports facts; every finding traces to an exact file:line, and the same PR always produces the same review. That's the whole point: LLM reviewers are smart but drift and cost tokens; Lenscheck is deterministic and free.

Does it run my code?

Never. It reads code statically and analyzes clean git snapshots (via git archive) in a temp folder — it doesn't execute anything or touch your working tree. Safe to point at any repo.

What about repos that aren't Django / DRF?

Its lens is endpoint-centric (Django & Django REST Framework today). On a repo it can't read, it simply finds nothing to report — it won't invent false alarms. More frameworks (FastAPI, …) are on the roadmap.

Do I need GitHub Advanced Security?

No. The review, PR comments, labels, gating, and the org roll-up all work without it. GHAS only powers the optional SARIF Security-tab dashboard.

Is it free?

Yes — under the Elastic License 2.0: free to use, self-host, and modify. You just can't resell it as a hosted service.

Get started in 30 seconds

Three commands, zero setup

Needs Python 3.8+, plus git and tar. That's the whole install.

1

Install it

pip install lenscheck-semantic-reviewer
2

Review a pull request

lenscheck review https://github.com/owner/repo --pr 481
3

Or explore it in your browser

cd your-repo && lenscheck serve