Skip to main content

LaNorme

PyPI Python CI Docs Licence: MIT

LaNorme makes a codebase's standard executable. It automates the mechanical side of code review: on every commit it checks cyclomatic complexity, file and function size, duplication, stale doc references, architectural boundaries, and naming conventions, and fails the build when they drift.

The built-in normes cover the common ground. A plugin interface lets a team encode its own, so the standard you agree on is the standard the build keeps. The same gate runs in CI and inside an AI agent's loop, so people and agents write to one bar and the codebase stays clean as it grows.

Standard library only. No runtime dependencies. Python 3.13+.

Full documentation: lanorme.github.io/lanorme. Tutorials, how-to guides, the complete rule and configuration reference, and agent-friendly Markdown: every page is also served raw at its .md URL, with an llms.txt index for agents.

Why LaNorme

  • Keep a growing codebase clean. Complexity, size, duplication, and stale docs are caught the moment they appear, while the fix is still small.
  • Enforce architecture and conventions. Layering, ports-and-adapters wiring, and your own domain vocabulary become checks any contributor can run.
  • Gate AI-generated code. Hand an agent the same normes your team codes to: it gets concrete, mechanical feedback on what good looks like here, and non-compliant output fails before it reaches a human.
  • Spend review on judgement. Reviewers focus on design and correctness because the mechanical checks are already green.

Install

uv tool install lanorme       # or: pipx install lanorme, pip install lanorme
uvx lanorme check .           # or run once without installing

Releases are tagged vX.Y.Z; see the releases page for notes and the documentation for other install methods.

Quickstart

lanorme check [PATHS...]               # run every enabled check (default path: .)
lanorme check . --check secrets        # run one check by name, rule code, or category
lanorme check . --select TYPE,AUTHN    # only these rule codes or categories
lanorme check . --output-format ndjson # one finding per line, for jq / grep
lanorme rules                          # list every registered rule

Exit code is 1 when any check fails, 0 when the tree is clean. By default a run reports only the checks that found something, then a summary line:

$ lanorme check src/
[FAIL] secrets
  VIOLATION: app.py:8 — Hardcoded credential value bound to 'API_KEY'
    Rule: SECRETPY-001: No hardcoded secrets in source code
    Fix: Read the value from an environment variable, secrets manager, or settings module
--- secrets: 1 violations, 0 warnings ---

Summary: 30 checks — 29 passed, 0 warnings, 1 failed.

Every command, flag and output format is documented in the CLI reference. Inline suppression (# noqa, or the ruff-safe # lanorme: ignore[CODE]) is covered in Configure which checks run.

What it checks

lanorme rules prints the live list. The rule reference documents every rule: what it catches and what it does not, its config, and its measured precision and recall on the bundled corpora.

On by default, on any project, no config needed:

Rule Catches
CMT-001/002 commented-out code, over-long comment blocks
DRY-001 near-duplicate function bodies
SIZE-001..003 / COMPLEXITY-001 / PARAM-001 file, function and class size; cyclomatic complexity; parameter count
IMPORT-001 / ENDPOINT-001 imports inside function bodies; deeply nested endpoints
NAMING-003/004 HTTP-verb-to-handler match; boolean-prefix predicates
NAMING-006..008 classes named as actions; functions that act but are not named verb-first; weak verbs (handle_, process_, do_)
TYPE-001..004 dict[str, Any], bare containers, untyped **kwargs; a missing return annotation (advisory)
AUTHN-001 / SQL-001 / SECRETPY-001 mutation endpoints without an auth dependency; raw SQL at a database call; hardcoded secrets in .py
SHELL-001 / DESERIAL-001 / EVAL-001 / CRYPTO-001 / TLS-001 / DEBUG-001 shell injection, unsafe deserialisation, eval/exec, weak hashes, disabled TLS, debug mode
JUNK-001/002 screenshots, scratch files, OS junk, stray binaries
TESTFILE-001 a production module with no test_*.py partner
META-001..005 the checks themselves emit well-formed output
SKILL-001..006 Agent Skill (SKILL.md) frontmatter, naming and link compliance

Also on by default, but firing only on a tree laid out as domain/, application/, infrastructure/ and api/ with ports under application/ports/: LAYER and PORT. Pick an architecture profile to set the layout you use, or ignore them.

Off until you turn them on (domain vocabulary, house styles, Markdown docs structure, and experimental precision-first detectors): TERM, KWARG, NAMING-001/002, NAMING-005, NAMING-009..011, AAA, CMT-005, CMT-006/007, SUPPRESS, SIMILAR, ATTR, PROSE, DOCS, PATH, STALE. The rule reference documents each.

Configuration

LaNorme walks up from the target path looking for config: a dedicated lanorme.toml, otherwise a [tool.lanorme] table in pyproject.toml. Command line flags win over both.

[tool.lanorme]
extends = ["strict", "hexagonal"]   # adopt bundled profiles; local keys win
select = ["ALL"]
ignore = ["NAMING-003"]
promote = ["TYPE-004"]              # advisory warnings become build-failing errors
exclude = ["postman/**", "vendor/*"]

That is the surface. The docs cover the rest without repeating it here:

Adopting on an existing codebase

A mature codebase has findings on day one. A baseline records the debt you already have so only new findings report; the whole adoption is one command and one config line:

lanorme baseline write    # records current findings to lanorme-baseline.json

Add baseline = "lanorme-baseline.json" under [tool.lanorme] and commit the file like a lockfile. From then on every check runs at full strictness, but only what you add reports. The full walkthrough is the adopt-on-an-existing-codebase tutorial.

Writing a check

A check is any object with name, description, rules, and a run method; drop it in lanorme/checks/, ship it under the lanorme.checks entry-point group, or point at it with [tool.lanorme] plugins = [...]. The write-a-check guide and CONTRIBUTING.md cover the setup, the gates, and the conventions for a new rule.

Versioning

The public surface is the rule codes you put in select / ignore / per-file-ignores and the config keys under [tool.lanorme]. The question that decides a bump is whether a green codebase could go red on upgrade: a patch keeps every result unchanged, a minor can newly fail a previously-passing codebase (every pre-1.0 breaking change lands here), and a major is the stability commitment. Every change is listed in CHANGELOG.md.

A rule's human-readable description is not part of that surface and may be reworded in a minor release. A file-level finding anchors on its description, so rewording one detaches the matching entries in a committed baseline and those findings report again until you run lanorme baseline write once. The run tells you when this has happened, naming the file and rule rather than letting old debt look new, and the changelog entry says so for the release that causes it.

Licence

MIT. See LICENSE.

Release files for lanorme 0.20.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lanorme 0.20.0
File Size Uploaded
lanorme-0.20.0.tar.gz 179.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lanorme 0.20.0
File Interpreter ABI Platform
lanorme-0.20.0-py3-none-any.whl Python 3 none any Details

Total release size: 375.2 kB

Release files / lanorme-0.20.0.tar.gz

Download URL lanorme-0.20.0.tar.gz
Size 179.1 kB
Tags Source
SHA-256 checksum
How to use checksums
bbe519aa75c15f3ce9fb4f27fa3e61ddc76548211fe4b1eb22e4e54f053e387e
BLAKE2b-256 checksum
How to use checksums
6b036494ace79632b0b21e006f46ce3e57be086928f93fcb96768c109b6a4e6a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / lanorme-0.20.0-py3-none-any.whl

Download URL lanorme-0.20.0-py3-none-any.whl
Size 196.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36f522471d3dfdeeb0794e4692690336f614b9f5b44fe606cf91ad8b88d624d2
BLAKE2b-256 checksum
How to use checksums
561f6f94238b3c02cd89f706043f9c5647bc767644d424a0e4852f89d545f8f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.21.0

2 release files

This release

0.20.0 This release

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.14.2

2 release files

0.14.1

2 release files

0.14.0

2 release files

0.13.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page