Executable codebase standards for Python: quality, style, architecture, and structure, checked mechanically on every commit.
Project description
LaNorme
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: 24 checks — 23 passed, 0 warnings, 1 failed.
The full command and flag reference and output formats are in the
CLI reference.
Inline suppression, # noqa and the ruff-safe # lanorme: ignore[CODE], is 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 |
TYPE-001..003 |
dict[str, Any], bare containers, untyped **kwargs |
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 |
Off until you turn them on (layered or hexagonal architecture, domain
vocabulary, house styles, Markdown docs structure, and experimental
precision-first detectors): LAYER, PORT, TERM, KWARG, NAMING-001/002,
AAA, CMT-005, 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:
- Configuration reference: every key, its type and default, plus a machine-readable JSON schema.
- Profiles (
extends): thestrict,hexagonal,cleanandlayeredbundles. - Per-directory config:
drop a nested
lanorme.tomlto tighten one subtree while the rest stays lenient.
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 two commands:
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.
Licence
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lanorme-0.14.2.tar.gz.
File metadata
- Download URL: lanorme-0.14.2.tar.gz
- Upload date:
- Size: 133.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b8645595589f0d89b39d2a8048f0940c107ba2a96a2ddaa785fe8e4aca6ca61
|
|
| MD5 |
7380fda4596f7bd5208047c9385799bf
|
|
| BLAKE2b-256 |
2f9e76dc4ab4152dde5182ca73d726e9836f3c28c0349e6d1bd2dbba0c4f7219
|
Provenance
The following attestation bundles were made for lanorme-0.14.2.tar.gz:
Publisher:
release.yml on lanorme/lanorme
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lanorme-0.14.2.tar.gz -
Subject digest:
8b8645595589f0d89b39d2a8048f0940c107ba2a96a2ddaa785fe8e4aca6ca61 - Sigstore transparency entry: 1941210895
- Sigstore integration time:
-
Permalink:
lanorme/lanorme@15920e76eb035f46df919a02ead06ae829c89789 -
Branch / Tag:
refs/tags/v0.14.2 - Owner: https://github.com/lanorme
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@15920e76eb035f46df919a02ead06ae829c89789 -
Trigger Event:
release
-
Statement type:
File details
Details for the file lanorme-0.14.2-py3-none-any.whl.
File metadata
- Download URL: lanorme-0.14.2-py3-none-any.whl
- Upload date:
- Size: 151.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57bd26993546370a55cc59e5cd6e9ee574cb952c76def33b02f954e36523aa7f
|
|
| MD5 |
636d7acf7759f6f5777346b4ed0dc6d0
|
|
| BLAKE2b-256 |
3f0a465d18c1a5e20a78c6bf2f9f27d62efa5b038c8497928166d8f956bf9b29
|
Provenance
The following attestation bundles were made for lanorme-0.14.2-py3-none-any.whl:
Publisher:
release.yml on lanorme/lanorme
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lanorme-0.14.2-py3-none-any.whl -
Subject digest:
57bd26993546370a55cc59e5cd6e9ee574cb952c76def33b02f954e36523aa7f - Sigstore transparency entry: 1941211003
- Sigstore integration time:
-
Permalink:
lanorme/lanorme@15920e76eb035f46df919a02ead06ae829c89789 -
Branch / Tag:
refs/tags/v0.14.2 - Owner: https://github.com/lanorme
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@15920e76eb035f46df919a02ead06ae829c89789 -
Trigger Event:
release
-
Statement type: