Lenscheck documentation

Two packages. Every command, flag, and CI recipe in one place.

Install

PackageInstallWhere it runs
lenscheck-semantic-reviewerpip install lenscheck-semantic-reviewerYour CI (a GitHub Action / CLI). Never imported by your app.
lenscheck-contractpip install lenscheck-contractInside your Django app (a real dependency).

The reviewer needs Python 3.8+, plus git and tar. The contract needs Python 3.10+ and, for the Django features, django>=4.2.

Two vantage points

lenscheck-semantic-reviewerlenscheck-contract
Seesthe PR diff, statically (AST)the real app at startup + runtime
Outputa comment on the PRfails CI (diff) + blocks calls (guard)
Setupnone — add the Actionexport in CI + optional middleware
Imported?no — it's a toolyes — decorators + middleware

🔎 lenscheck-semantic-reviewer

A semantic PR reviewer for Python web apps. It reads the diff, tells you what it means and where to look, and can enforce a confirmed invariant corpus — no LLM, no API key. CLI entrypoint: lenscheck.

lenscheck review

Run a semantic review of a PR or commit range. With no repo, it reviews the current git repo's branch vs. its default branch.

lenscheck review <repo|url> --pr 128 --out pr_review.md --json pr_review.json --sarif pr_review.sarif
FlagWhat it does
repoPositional. Local path or git URL. Defaults to the current git repo.
--pr NReview a GitHub PR by number (needs a GitHub URL repo).
--commit SHAReview a single commit (its diff vs its parent).
--merge SHAReview a merge commit (base ^1, head ^2).
--base / --headExplicit base..head range.
--out FILEMarkdown review. Default pr_review.md.
--json FILEStructured review JSON (drives inline comments + labels).
--html FILESelf-contained HTML viewer.
--sarif FILESARIF 2.1.0 for GitHub code scanning.
--invariants FILEConfirmed invariant corpus JSON to enforce (see the bridge).
--fail-on {violation,crit}Exit non-zero on 🔴 invariant violations (violation) or any 🔴 (crit).

lenscheck post

Post a review to a PR — a sticky summary comment, optional inline comments, and one triage label.

lenscheck post 128 pr_review.md --json pr_review.json --inline --label
ArgWhat it does
pr filePositional: the PR number and the markdown review file.
--json FILEThe review JSON (required for --inline / --label).
--inlinePin each finding as an inline comment on the exact changed line.
--labelApply one lenscheck:* triage label for the PR's top severity.

lenscheck serve

Start the Lenscheck web UI to explore a repo/PR in the browser.

cd your-repo && lenscheck serve --port 8765
Flag / envWhat it does
--repo · LENSCHECK_DEFAULT_REPORepo to open.
--invariants · LENSCHECK_INVARIANTSInvariant corpus to enforce in the UI.
--host · HOSTBind host. Default 0.0.0.0.
--port · PORTBind port. Default 8765.
--pr / --commit / --merge / --base / --headOpen this target on load.
--no-openDon't open a browser window.
LENSCHECK_TOKEN · LENSCHECK_ALLOWED_REPOS · LENSCHECK_BASE_PATHAuth token, repo allowlist, and base path when hosting the UI.

lenscheck invariants

Discover candidate invariants from a repo's history (ranked by how persistently they held), for a human to confirm into a corpus. the reviewer's own discovery — see also lenscheck-contract invariants

lenscheck invariants <repo> --snapshots 40 --out invariants.discovered.json
repoRepo to sample.
--snapshots NHow many commits across history to sample.
--out FILEDiscovered corpus. Default invariants.discovered.json.

lenscheck digest

Org-wide leadership roll-up from the labels Lenscheck applies to PRs.

lenscheck digest --org my-org --repos repos.txt --slack $SLACK_WEBHOOK
--orgRequired. GitHub org/owner to summarize.
--repos FILEOptional file of owner/repo lines → adds an adoption line.
--slack URLSlack incoming-webhook to post the digest to.

GitHub Action

The zero-setup path. Add it to a pull_request workflow.

name: lenscheck
on: pull_request
permissions: { contents: read, pull-requests: write, issues: write }
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: AnkushSinghGandhi/lenscheck-semantic-reviewer@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          fail_on: crit          # violation | crit | none
          inline: true
          label: true
InputDefaultWhat it does
github_token${{ github.token }}Token for PR data + posting the comment.
fail_onnoneviolation, crit, or none — when to fail the build.
invariantsPath to a confirmed invariants JSON to enforce.
commenttruePost the review as a sticky PR comment.
inlinetruePin each finding on its exact changed line.
labeltrueApply one lenscheck:* triage label (needs issues: write).
scan_depstrueScan bumped dependencies for new capabilities via PyPI.
upload_sariftrueUpload SARIF to code scanning (needs GitHub Advanced Security on private repos; set false without it).

Outputs: review_markdown (pr_review.md) and review_sarif (pr_review.sarif).

🛡️ lenscheck-contract

Your Django backend keeps a list of what it does — and can't lie about it. It records routes, models, and effects at runtime, diffs that contract in CI, and blocks undeclared effects while the app runs. CLI entrypoint: lenscheck-contract.

Decorators

Optional — Level 1 works with zero code. Declare what matters and the guard can enforce it.

from lenscheck_contract import contract

@contract.route("POST /orders", auth="user")     # method + path + who may call it
@contract.effects("net:api.stripe.com", "email") # every outside call it's allowed to make
def create_order(request):
    ...

@contract.job("nightly-invoices", schedule="0 2 * * *")
@contract.effects("net:api.stripe.com")
def nightly(): ...
DecoratorWhat it declares
@contract.route("METHOD /path", auth=...)A route and its required auth (public, user, …).
@contract.effects(*patterns)Allowed effects. Patterns: net:host, net:*.stripe.com, net:*, email.
@contract.job("name", schedule=...)A background job and its (optional) cron schedule.

Guard & middleware

The guard hooks the socket layer once — so requests, httpx, urllib, boto3, smtplib and anything else are all covered. An undeclared call in error mode never leaves the process.

# settings.py
MIDDLEWARE = ["lenscheck_contract.middleware.ContractMiddleware", ...]

# apps.py / conftest.py
from lenscheck_contract import guard
guard.install(mode="error", allow=["net:*.internal"])   # off | record | warn | error
ModeBehaviour
offDo nothing.
recordObserve only — collect what actually happens (for suggest).
warnWarn on an undeclared effect, but allow it.
errorRaise UndeclaredEffect — the call is blocked.

Don't know what to declare? Run tests in record mode, dump guard.suggestions(), and feed it to lenscheck-contract suggest.

lenscheck-contract export

Write the contract as JSON. Reads Django's real router + model registry — routes built in loops and DRF routers included.

lenscheck-contract export --settings myproj.settings -o contract.json
-o, --output FILEWhere to write. Omit to print to stdout.
--settings MODDJANGO_SETTINGS_MODULE.
--root DIRProject root to put on sys.path. Default .
--import-module MODExtra modules to import (repeatable) so their decorators run.
--no-djangoDeclarations only; skip Django probing.

lenscheck-contract diff

Compare two contract files (base vs head) and print what a reviewer cares about. This is the CI gate.

lenscheck-contract diff base.json head.json --markdown --fail-on risky
base headPositional: the two contract JSON files.
--markdownFormat for a PR comment.
--fail-on {never,risky,review,any}Exit 1 at this severity. Default never. risky = auth weakening, new egress, dropped fields, relaxed uniqueness.

lenscheck-contract invariants

Emit a confirmed-invariant corpus for the reviewer's --invariants. This is the bridge — see below.

lenscheck-contract invariants --settings myproj.settings -o invariants.json
-o, --output FILEWhere to write the corpus.
--settings / --root / --import-module / --no-djangoSame as export.

lenscheck-contract suggest

Turn a recorded run into ready-to-paste decorators.

lenscheck-contract suggest observed.json     # prints @contract.effects(...) per handler
observedJSON produced by guard.suggestions() after a record-mode run.

🔗 The bridge: runtime truth → the reviewer

On its own the reviewer guesses your invariants from git history. lenscheck-contract hands it the truth: your app's declared egress destinations become an enforced allowlist, and its auth/PII rules are confirmed. A brand-new domain sneaking into a PR then becomes a 🔴 critical alert — grounded in fact.

# 1. export the real contract as an invariant corpus (runs your app)
lenscheck-contract invariants --settings myproj.settings -o invariants.json

# 2. hand it to the reviewer — it stops guessing, it enforces
lenscheck review <repo> --pr 128 --invariants invariants.json --fail-on violation
What gets enforced: external-egress-allowlist (your declared net: destinations), auth-before-write, and pii-egress-authed — all marked confirmed, so the reviewer treats them as hard rules, not hunches.

Full CI — both tools on one PR

name: pr-checks
on: pull_request
permissions: { contents: read, pull-requests: write, issues: write }

jobs:
  # 1) the reviewer — comments on the PR (soft heads-up)
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: AnkushSinghGandhi/lenscheck-semantic-reviewer@v1
        with: { github_token: "${{ secrets.GITHUB_TOKEN }}", fail_on: crit }

  # 2) the contract — hard gate on structural change (+ feeds the reviewer)
  contract:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - run: pip install -e . lenscheck-contract
      - run: lenscheck-contract export --settings app.settings -o head.json
      - run: lenscheck-contract invariants --settings app.settings -o invariants.json
      - run: git checkout ${{ github.base_ref }}
      - run: lenscheck-contract export --settings app.settings -o base.json
      - run: lenscheck-contract diff base.json head.json --markdown --fail-on risky

The third catch — the runtime guard — runs in your test/staging job, because it needs the app actually running (guard.install(mode="error")).

← Back to home  ·  Reviewer on GitHub  ·  Contract on GitHub