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, Flask, or FastAPI app (a real dependency).

The reviewer needs Python 3.8+, plus git and tar. The contract needs Python 3.10+ and your framework as an optional extra โ€” pip install "lenscheck-contract[django]", [flask], [fastapi], or [celery].

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. CLI entrypoint: lenscheck (needs a beta API key; your source never leaves โ€” only facts are sent).

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 (on-prem / self-hosted build)

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@v2
        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.
invariantsโ€”Path 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 Python backend โ€” Django, Flask, or FastAPI โ€” keeps a list of what it does, and can't lie about it. It records routes, models (Django, SQLAlchemy, Pydantic), effects, and Celery jobs at runtime, diffs that contract in CI, and blocks undeclared effects while the app runs (sync and async). 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, sync and async (it hooks the asyncio event loop too, so httpx.AsyncClient / aiohttp are caught in-context). An undeclared call in error mode never leaves the process.

Install the request hook for your framework so every view is watched (not just decorated ones), then turn the guard on:

# Django โ€” settings.py
MIDDLEWARE = ["lenscheck_contract.middleware.ContractMiddleware", ...]

# Flask
from lenscheck_contract import install_flask
install_flask(app)

# FastAPI
from lenscheck_contract import install_fastapi
install_fastapi(app)

# then, anywhere at startup (apps.py / conftest.py / main.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 your framework's real router + models โ€” routes built in loops, DRF routers, Flask blueprints, and FastAPI dependency trees included.

lenscheck-contract export --settings myproj.settings -o contract.json   # Django
lenscheck-contract export --no-django --app myapp.main:app -o contract.json   # Flask / FastAPI
-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@v2
        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