Skip to main content

lanekeep

Deterministic, AST-based architectural conformance checking for AI-generated and human-written code.

crates.io npm PyPI CI License: MIT OR Apache-2.0 MSRV

lanekeep enforces the conventions that live in your team's heads and your reviewers' comments — the ones a language model cannot infer from the code it is shown. Every rule is a codified answer to "the agent keeps doing this wrong."

It ships as a single static binary with no runtime dependency.


Languages

Each guide covers installing lanekeep in that ecosystem, configuring it, the built-in rules that apply, a worked custom rule, and the name-resolution behavior specific to that language.

Language Extensions Install Guide
Go .go go get -tool github.com/fmsouza/lanekeep/cmd/lanekeep Go
Python .py, .pyi pip install lanekeep Python
Rust .rs cargo install lanekeep-cli Rust
TypeScript / JavaScript .ts, .mts, .cts, .tsx, .js, .mjs, .cjs, .jsx npm install --save-dev lanekeep TypeScript and JavaScript

brew install fmsouza/tap/lanekeep works anywhere, as does a binary from the releases page. Every channel delivers the same build, so the bytes are identical whichever you pick.

Whatever the project, the first two commands are the same:

lanekeep init     # detects the project and writes a config plus a starter rule
lanekeep check

New here? Getting Started is about a minute, end to end.

What it is

lanekeep is not a linter in the ESLint sense. ESLint enforces language-level correctness; lanekeep enforces project-specific conventions. The two barely overlap, and lanekeep replaces neither your linter nor your formatter.

A rule is a program, not a configuration entry. It declares a tree-sitter query that Rust matches at native speed, and a handler that runs only on matches — where it can loop, accumulate state, read other files and ask where a name came from.

That matters because the conventions worth enforcing are the ones specific enough that nobody else would ever write them, which is exactly the population a fixed vocabulary of predicates fails. See Writing Rules for the anatomy and the full host API, and each language guide for a worked example in that language.

Three things follow from who reads the output:

  • Every rule carries its own fix. message, remediation and examples are mandatory fields, not documentation — they are the card fed back to whoever has to act on the violation, increasingly an agent.
  • Output is deterministic. Violations are always sorted by (ruleId, file, line, column), and the sandbox withholds the clock and randomness, so two runs over identical input produce byte-identical output. An agent reading it twice must not see reordering as change.
  • It runs in the inner loop. Agents and developers invoke it after every edit, so a warm run is measured in tens of milliseconds. The built-ins that ship as WebAssembly components are all under 115 KB, so loading them is noise — a 12.4 MiB compiled-TypeScript component that once cost new TypeScript projects ~6.5 seconds on their first run was reverted for exactly that reason. docs/architecture.md §15 has the ledger.

Rules are authored in TypeScript whatever language they check — that is the form to start from, and it is the one most teams already have someone who writes. A rule may also be a WebAssembly component, which is how four of the sixteen built-ins ship — two written in Rust and two written in Go; the other twelve run as QuickJS modules, three of them checking five of the six supported languages from a single source (every one but JavaScript). Every form reaches the same host API and is held to the same limits, and a config names a rule rather than its implementation. Configuration is neither — lanekeep.json is plain data, so a Go, Python or Rust team never writes a .ts file except when authoring an actual rule.

Using it

lanekeep check                  # the whole project
lanekeep check --staged         # only what is about to be committed
lanekeep check --since main     # only what changed against a ref
lanekeep check --file src/a.ts  # exactly these files, repeatable, no git involved
lanekeep check --watch          # re-check on every change, until Ctrl-C
lanekeep check --fix            # apply the safe fixes, report what is left
lanekeep check --profile        # per rule: where the time went, and what it looked at
lanekeep rules                  # what this project has configured
lanekeep explain <rule-id>      # one rule's card, without opening its source
lanekeep server                 # LSP for an editor, or --protocol mcp for an agent host

Exit codes: 0 clean, 1 violations found, 2 the checker could not run. A caller has to be able to tell "your code has problems" from "the tool is broken". --warn-only reports violations but exits 0, for a phased rollout.

Output formats via --format: human (default), json (versioned, stable schema), sarif (GitHub code scanning), and agent — token-minimal, grouped by rule rather than by file. Diagnostics always go to stderr, so piping into a parser works even when something fails.

Fixes are applied only when the rule marked them behavior-preserving; anything else is shown and never written. Suppressions carry a mandatory reason and an optional expiry, and a directive that does not work says so rather than silently doing nothing.

Configuration reference, CI recipes, editor setup and the MCP tool list are in the wiki.

How it stays fast with programmable rules

The usual problem with a native tool that runs JavaScript plugins is the boundary between them: dispatching into JS once per AST node means tens of thousands of crossings per file.

lanekeep dispatches once per query match instead. The query runs in Rust across a single shared parse; only matches reach your handler. That is typically two to three orders of magnitude fewer crossings, and it is the reason a Rust engine still earns its place once rules are programs.

discover paths (globs, gitignore-aware)
  └─> for each file, in parallel:
        cache key ──hit──> validate tracked deps ──> cached violations + facts
                  └─miss─> path and raw-text gates reject before any parse
                           └─> parse once ─> match queries in Rust
                               └─> invoke the handler, per match only
  └─> reduce phase: cross-file rules consume facts only, never parse trees
  └─> filter suppressions ─> sort ─> report

A warm run with no changes executes no JavaScript at all — every file is a cache hit.

Platforms

Prebuilt for macOS on Apple silicon, Linux on x86-64 and arm64, and Windows on x86-64. The Linux binaries are built against glibc 2.17, so they run on anything from RHEL 7 onwards.

No runtime is required to run lanekeep. Node, Python or Go is needed only to install it from that ecosystem, where it picks which binary to fetch. Nothing is pulled in as a dependency any of those ways.

Intel macOS is not prebuilt — cargo install lanekeep-cli builds it from source, and both the npm launcher and the Homebrew formula say so rather than failing obscurely.

Security

lanekeep is meant to run as a pre-commit hook and inside CI, which makes it a supply-chain target. Rules are executable code, so the posture is about confinement rather than absence:

  • No ambient authority. A TypeScript rule runs in an embedded QuickJS sandbox and a WebAssembly rule under wasmtime; both reach exactly the host functions lanekeep exposes. fs, process, child_process, network and dynamic import are not restricted — they do not exist in the context. A component imports one interface and is refused at load if it imports another.
  • No network access. Ever, in any mode, with no configuration that enables it.
  • Filesystem confinement. Reads go through a tracked host call, confined to the project root. Writes happen only under --fix, only to matched files, only within reported ranges.
  • Bounded execution. A per-invocation timeout, a global run budget and a per-runtime memory ceiling, none disableable — a rule that hangs a pre-commit hook is indistinguishable from a broken tool. Breaching any of them cancels the run and exits 2, rather than reporting a partial result as a clean one.
  • Deterministic by construction. The sandbox withholds the clock and randomness, so a rule cannot introduce nondeterminism even by accident.

This bounds blast radius and makes third-party rule sets reviewable. It is not a boundary against someone who can already commit to the repository being checked. To report a vulnerability, see SECURITY.md.

Project status

Released and usable, on every channel in the table above — one build feeding all of them.

It is 0.x, and this repository treats that as semver does: a minor bump may break a public Rust API. Rule authors are insulated from that — host API methods and the config shape are additive — but pin a version if you embed the crates.

Known gaps, stated rather than implied:

  • Two of the three performance budgets in docs/architecture.md §15 are not met. The cold budget is; they are targets, and that section says by how much and where the remaining time goes.
  • No general type inference, by design. Name resolution is syntactic; a rule that opts in (requires: ['types']) gets a bounded within-file oracle that answers undefined rather than guess — see §1 non-goals and §6.10.

Documentation

The wiki is the place to start — it is task-shaped and organized by language.

Page Purpose
Getting Started Install and catch something, in about a minute
Configuration lanekeep.json, every field
Writing Rules Rule anatomy and the full host API
CI and Editors Pre-commit, GitHub Actions, LSP, MCP

In-repo, versioned with the code:

Document Purpose
docs/architecture.md The full design: execution model, host API, cache, milestones
docs/built-in-rules.md The rules lanekeep ships with, and their options
docs/cross-file-rules.md Writing a rule that needs a whole-corpus view
docs/obligation-rules.md Writing a rule that needs a resource released on every path
docs/type-aware-rules.md Writing a rule that needs to know what a value's type is
docs/authoring-rust-rules.md Writing a rule in Rust, shipped as a WebAssembly component
docs/authoring-go-rules.md Writing a rule in Go
docs/authoring-python-rules.md Writing a rule in Python
CONTRIBUTING.md Setup, commands, and the pull request process
AGENTS.md How to work in this repository — for coding agents and humans alike
SECURITY.md Threat model and how to report a vulnerability
docs/releasing.md How a release is built, gated and published
CHANGELOG.md What changed, per release

Contributing

Contributions are welcome, particularly new built-in rules and new host API surface. Start with CONTRIBUTING.md — ./scripts/setup-dev.sh installs everything and wires the git hooks, and just check is the same gate CI runs.

All work ships as squashed pull requests with Conventional Commits titles. main is protected and takes no direct pushes.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Metadata

Release files for lanekeep 0.10.0

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

Built distributions (wheels)

Table of built distributions (wheels) for lanekeep 0.10.0
File
lanekeep-0.10.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
lanekeep-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
lanekeep-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
lanekeep-0.10.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 27.6 MB

Release files / lanekeep-0.10.0-py3-none-win_amd64.whl

Download URL lanekeep-0.10.0-py3-none-win_amd64.whl
Size 7.5 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
30c902d138341cb8889a5429cfb7f278aa8bf3a2ab5cb6a8a445723b89f5e16f
BLAKE2b-256 checksum
How to use checksums
03afc7f736c35a4de3f291683629df1e0d6032a813c2abd450acaa85cdb49181
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / lanekeep-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL lanekeep-0.10.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 7.4 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
fca53647cf054596533b855e79bba66cee8434747e43df2fad2441d518fb8c10
BLAKE2b-256 checksum
How to use checksums
9d42ffa897f983509cbfe5da85e486e58d022f2e357688be20ef8f151e0d1aa8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / lanekeep-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL lanekeep-0.10.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 6.4 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
ed9fbd1ba2f7cfdae244fa41c0b5de946040f36d16bd75b34faf6d798eb386ab
BLAKE2b-256 checksum
How to use checksums
366e5190745a7f7f904575ce43cc9930cf59738f89a3973df4040fae23a75dd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / lanekeep-0.10.0-py3-none-macosx_11_0_arm64.whl

Download URL lanekeep-0.10.0-py3-none-macosx_11_0_arm64.whl
Size 6.2 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
25f4eb5b0cbeaff5c784c916f99f3d0ac290532995730f1cf439063e3913883e
BLAKE2b-256 checksum
How to use checksums
1a87a21a261e6ea07c5831e89029b6f5fff52b3c54367666e587b53b9032439e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

0.12.0

4 release files

0.11.0

4 release files

This release

0.10.0 This release

4 release files

0.9.0

4 release files

0.8.1

4 release files

0.8.0

4 release files

0.7.0

4 release files

0.6.1

4 release files

0.6.0

4 release files

0.5.0

4 release files

0.4.0

4 release files

0.3.2

4 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