/ user guide

Everything you need to run Archlint.

Install, declare your architecture, and let the guardrail catch drift while your agent writes code. One Python file, standard library only — no daemon, no service, no LLM in the hot path.

Archlint is a guardrail that catches architectural drift while your AI coding agent writes code — not hours later in CI. It reads the layer rules you already keep in your project's CLAUDE.md (or a dedicated ARCHITECTURE.md) and checks every edited file's imports against them, in the agent's loop, while there's still a cheap chance to course-correct. It's the seatbelt, not the steering wheel: it surfaces drift, the agent decides what to do.

/ requirements

Requirements

  • Python 3.8+ — standard library only, nothing to install.
  • A supported agent harness: Claude Code (full per-edit feedback) or Cursor (end-of-turn feedback). Archlint also runs standalone for CI and pre-commit.
  • Works on macOS, Linux, and Windows.
/ install

Install

Unzip the bundle and run the installer — one command, no JSON to edit.

global install
# works in every project unzip archlint-*.zip && cd archlint python3 archlint.py install

This installs Archlint as a Claude Code plugin under ~/.claude/skills/archlint/, so the hooks and the /archlint:* commands are available everywhere. It's a safe no-op in any project that hasn't declared rules. Start a new Claude Code session to activate the hooks.

Per-project (committable, shared with your team)

per-project
python3 /path/to/archlint/archlint.py install --project . git add .claude/

This writes the hook and the settings.json block into .claude/, so the whole team and CI share the same guardrail. If your rules live in a dedicated ARCHITECTURE.md rather than inline in CLAUDE.md, the installer also adds the line that loads them into the agent's context — @ARCHITECTURE.md — idempotently, never duplicating it.

Cursor

cursor
python3 /path/to/archlint/archlint.py install --harness cursor .

Cursor's per-edit hook is observational, so in Cursor Archlint surfaces the turn's drift at end-of-turn, which Cursor feeds back as the next message. Cursor reads AGENTS.md natively, so keep your rules block there.

No bundle? Drop the single file into .claude/hooks/archlint.py, mark it executable, and wire the PostToolUse and Stop hooks in .claude/settings.json (an example ships as settings.example.json).
/ the blueprint

The blueprint

Archlint does nothing until a project declares its architecture. Add an ## Architecture section to your CLAUDE.md — or to AGENTS.md, ARCHITECTURE.md, SPEC.md, or ROADMAP.md. Inside it, a fenced archlint block declares your layers and rules:

ARCHITECTURE.md
layer routes = src/routes/**, src/api/** layer services = src/services/** layer repos = src/repos/** layer ui = src/ui/**, src/components/** routes -> services services -> repos ui -> services forbid ui -> repos forbid! ui -> db/secrets # always blocks forbid * -> src/legacy/**

Grammar

StatementMeaning
layer NAME = glob, ...Define a layer by one or more file globs.
X -> YX is allowed to depend on Y.
forbid X -> YExplicit prohibition (overrides allow).
forbid! X -> YHard prohibition — always blocks, even in lenient mode.
forbid * -> path/**Anything depending on path/... is forbidden.
set max-file-lines NOversized-source-file smell threshold (default 500).
set detect-cycles on|offLayer-cycle detection (default on).

One statement per line; # starts a comment. Imports of third-party packages (npm, pip, stdlib) are ignored — no false positives for dependencies you don't own. A line that doesn't parse is never silently dropped: Archlint surfaces it, so a typo'd forbid can't quietly disable itself.

Where it lives

The first host that actually carries a rule-bearing archlint block wins, with a dedicated ARCHITECTURE.md preferred over CLAUDE.md. Keeping rules in a dedicated file keeps a large CLAUDE.md lean — just add @ARCHITECTURE.md to CLAUDE.md (the installer does this) so the agent loads them. If more than one file declares a block, Archlint warns rather than guessing silently. Override the location with the ARCHLINT_BLUEPRINT environment variable.

Don't write the rules cold

Let Archlint draft a starting blueprint from your existing tree:

archlint init
python3 archlint.py init # print a draft python3 archlint.py init --write # write it into your blueprint

init infers layers from your directory structure and edges from imports that actually exist. It emits no forbid rules — those are intent, and only you know them. Run it, then review: prune edges that shouldn't exist and add the prohibitions that encode your real boundaries. --write refuses to clobber an existing block.

/ how it works

How it works

While your agent edits the project, Archlint runs at two moments:

  • After every Write/Edit — it parses the edited file, extracts its imports, and checks them against your layer rules. If a forbidden boundary is crossed, it surfaces a short, actionable note.
  • At the end of every turn — it emits a checkpoint of any unresolved drift, so the agent has one last chance to fix it before declaring done.

In lenient mode (the default), drift is reported as context and the agent tends to self-correct on the next step. In strict mode, drift becomes a hard block — the agent must keep working until the violation is gone. A forbid! rule always blocks, regardless of mode, for the boundaries that are non-negotiable.

/ command reference

Command reference

Archlint is also a CLI — the same engine and rules as the live hook.

CommandWhat it does
checkScan the whole project. --format sarif emits SARIF 2.1.0; default text mode exits 1 on drift (a CI / pre-commit gate).
initDraft a blueprint from the tree's layers and observed edges. --write writes it.
baselineRecord current findings as accepted, so only new drift is reported.
graphRender the layer graph — Mermaid (default), --format dot, or --out layers.svg.
reportThe self-correction rate from the drift log. --json for machine output.
installWire up the hooks — global, --project, or --harness cursor.

The graph output is colored to the brand — allowed grey, observed-but-undeclared amber, forbidden red, cycles magenta — and Mermaid ships inside a fence, so it drops straight into a README or PR comment and renders. report turns the drift history into the one number nobody else has:

archlint report
# how often the guardrail worked self-correction rate : 87% (resolved before stopping)
/ beyond forbidden edges

Beyond forbidden edges

  • Layer cycles. Archlint reports cyclic layers (a -> b -> a) from your real imports — the structural smell a layered blueprint is uniquely positioned to catch — and names a concrete import realizing each edge, so it's fixable without grep.
  • Hard rules. forbid! X -> Y always blocks, even in lenient mode.
  • Public-API leakage. An allowed cross-layer import that reaches into a private module (_helpers, an internal/ package) is flagged as a smell.
  • Oversized files. Source files over the max-file-lines threshold are flagged. Only source code is checked — logs and docs are not.
/ modes & environment

Modes & environment variables

VariableValuesEffect
ARCHLINT_MODElenient (default), strictstrict makes drift block the agent; lenient just notifies.
ARCHLINT_BLUEPRINTpathOverride the blueprint location.
ARCHLINT_QUIET1Silence non-blocking feedback; hard/strict blocks still surface.
/ ci & code scanning

CI & GitHub code scanning

check speaks SARIF 2.1.0, the format GitHub code scanning consumes — so a violation shows up as an inline annotation on the exact line of the PR diff, not a buried log line. Drop this workflow in at .github/workflows/archlint.yml (it ships in the bundle); it runs the archlint.py you already committed, so there's no marketplace Action to trust:

.github/workflows/archlint.yml
- run: python3 .claude/hooks/archlint.py check --format sarif > archlint.sarif - uses: github/codeql-action/upload-sarif@v3 with: { sarif_file: archlint.sarif }

For a plain pre-commit gate, python3 .claude/hooks/archlint.py check exits 1 on drift — wire it straight into .git/hooks/pre-commit.

/ legacy adoption

Adopting on a legacy codebase

A legacy project lights up with historical violations on day one — and nobody enables a linter that starts 50 findings in the red. Grandfather them:

archlint baseline
python3 archlint.py baseline git add .archlint/baseline.json

From then on, the hook and check report only new drift. Commit the baseline so the whole team and CI share the same starting line. Re-run baseline to re-accept the current state after an intentional restructure; delete it to see everything again.

/ languages

Supported languages

Import extraction works for Python, TypeScript/JavaScript, Go, Rust, Java, and C#. Archlint understands the import shapes real projects use — TypeScript path aliases (@/services/x), Python src/ layouts, Go module prefixes, Rust crate/self/super paths, Java fully-qualified names, and C# namespaces. Third-party and unresolved imports are simply skipped, so you never get a false positive for a package you don't own.

/ limitations

Limitations

By design, Archlint is honest about what it isn't:

  • It works on imports and globs, not semantics. It catches structural drift — a forbidden import boundary, a layer cycle — not "this function name suggests it belongs in another layer." That would need an LLM, which we keep off the hot path.
  • It doesn't fix things. It surfaces drift; the agent decides what to do. The seatbelt, not the steering wheel.
  • It needs a blueprint. No declared layers, no enforcement — Archlint stays a silent no-op until you tell it your architecture.
/ lite vs pro

Lite vs Pro (open core)

Archlint is open core. The free lite tier — layers, ->/forbid rules, blueprint diagnostics, the oversized-file check, the Claude Code hook (lenient + strict), real-world import resolution, the four core languages (Python, JS/TS, Go, Rust), and check — is MIT and public at github.com/MKanhan/archlint.

The Pro bundle adds everything that turns the guardrail into a product: cycle detection with named witness edges, forbid! hard rules, public-API leakage, init / baseline / report / graph, multi-harness (Cursor), Java and C#, SARIF for GitHub code scanning, the dedicated-blueprint discovery + @import pointer, and one-command install. USD 20, one-time.

Get Archlint Pro — $20
/ licensing

Licensing

  • The lite tier is MIT.
  • The Pro bundle is licensed under the PolyForm Internal Use License 1.0.0 plus commercial terms: one named developer, unlimited projects, perpetual use of the purchased version, one year of updates, no resale or redistribution. The full text ships as LICENSE inside the Pro bundle.

No license keys, no DRM, no phone-home. The terms define permitted use; honoring them is the deal.

/ support & updates

Support & updates

Purchase includes one year of updates; your purchased version is yours perpetually. For questions, updates, or licensing: [email protected].

/ faq

FAQ

Does it phone home or send my code anywhere?

No. Archlint runs entirely locally — standard-library Python, no network calls, no telemetry. Your code never leaves your machine.

Is there a license key to manage?

No keys, no activation, no DRM. The bundle just works; the license terms govern use.

Will it slow down my agent?

No. There's no LLM in the hot path — Archlint parses imports and matches globs, effectively instantaneous on a single edited file.

What if my project has no CLAUDE.md?

Archlint stays a silent no-op until you declare an architecture. Put the block in a supported host, or run init to draft one.

Does it work outside Claude Code?

Yes — check runs standalone for pre-commit and CI (including GitHub code scanning via SARIF), and there's a Cursor adapter. Same engine, same rules.

Can I get a refund?

Refunds are handled manually on a case-by-case basis — reach out.

Windows?

Yes. Python 3, standard library, cross-platform path handling.

Deterministic · no LLM in the hot path · no latency
Support Built by M. Kanhan