Files such as CLAUDE.md and AGENTS.md are useful for explaining a project to coding agents.
They can describe decisions like:
- Use Supabase Auth instead of Firebase
- UI components must not access the database directly
- Authentication code requires manual review
- Keep dependencies pointing inward
The problem is that these are still instructions, not enforcement.
An agent can understand a rule and still violate it during a large change. The generated code may compile and pass its tests while quietly introducing an architectural dependency that the project was supposed to avoid.
Humans do this too. The problem is not specifically AI-generated code. AI agents simply make it easier to produce larger changes faster, which makes architectural drift easier to miss.
From instructions to executable rules
I wanted the most important architecture decisions to behave more like lint rules.
That led me to build ArchLint, a small open-source CLI that checks Git changes against architecture rules stored in the repository.
The basic flow is:
AI agent or developer changes code
↓
Git diff
↓
ArchLint
↓
PASS or BLOCKED
ArchLint does not need to know whether the change came from Claude Code, Codex, Cursor, or a human contributor. It evaluates the resulting Git diff.
A small example
A project can define its rules in .archlint.yml:
version: 1
rules:
- id: no-firebase
type: forbidden_dependency
packages:
- firebase
message: "Use Supabase Auth only."
- id: db-boundary
type: import_boundary
from:
- "src/components/**"
deny:
- "src/db/**"
message: "UI components must not access the database directly."
- id: protect-auth
type: protected_path
paths:
- "src/auth/**"
severity: warning
These rules express three different decisions:
- A forbidden package must not be introduced.
- UI components must not import database code.
- Changes to authentication code should be highlighted for review.
The configuration lives beside the code, so it can be reviewed and versioned like any other architectural decision.
Checking a change locally
From the root of a Git repository:
npx archlint-ai init
npx archlint-ai check
The first command creates a starter configuration. The second checks staged, unstaged, and untracked changes.
No account, API key, dashboard, or global installation is required.
A successful check looks like this:
✓ 3 rules passed
NO ARCHITECTURE DRIFT DETECTED
When a rule is broken, ArchLint reports the rule, file, evidence, and message:
[no-firebase]
src/auth/firebase.ts:1
Use Supabase Auth only.
Evidence: firebase
Severity: ERROR
Blocking architectural drift in pull requests
The same check can run in GitHub Actions:
name: ArchLint
on:
pull_request:
jobs:
archlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: npx archlint-ai check --base origin/${{ github.event.repository.default_branch }}
If a pull request introduces an error-level violation, the command exits with a non-zero status and the check fails.
At that point, the team has two choices:
- Fix the implementation so it follows the existing architecture.
- Change
.archlint.ymlbecause the architecture decision itself has intentionally changed.
The second option is important. Architecture rules should not be permanent by accident. They should be explicit and reviewable.
Why inspect the Git diff?
ArchLint primarily checks additions in the current diff rather than scanning the entire repository.
This has two practical benefits:
- A team can adopt it without fixing every existing architectural problem first.
- Pull requests are evaluated based on the new drift they introduce.
It also keeps the tool focused on one question:
Did this change make the architecture worse?
Why deterministic checks first?
An LLM could review a diff and decide whether it violates an architectural principle. That may become useful for rules that cannot be expressed structurally.
However, many important constraints do not require an LLM:
- Do not add this dependency.
- Do not import this layer from that layer.
- Flag every change under this path.
For these rules, deterministic checks are faster, cheaper, easier to understand, and produce repeatable results.
ArchLint v0.1 therefore focuses on deterministic rules. It defines an experimental provider-neutral interface for future semantic checks, but the current package does not send repository code to an LLM.
Current limitations
ArchLint is still an early release.
The current dependency and import checks focus on JavaScript and TypeScript syntax. Import-boundary rules are path-based, and the project does not yet attempt to understand every framework or programming language.
That is intentional. I wanted to start with a small tool that solves a clear problem before expanding the rule system.
The next rules should be driven by real projects rather than guesses.
Architecture instructions still matter
ArchLint is not intended to replace CLAUDE.md, AGENTS.md, architecture decision records, or code review.
Those documents explain the reasoning and help agents make better choices.
ArchLint handles the smaller set of decisions that are important enough to enforce automatically.
A useful separation is:
Documentation explains the architecture.
ArchLint protects its critical boundaries.
Code review handles context and judgment.
The project is available on GitHub:
https://github.com/errrt/archlint
It can be tried with:
npx archlint-ai init
npx archlint-ai check
I would especially like feedback from developers using coding agents on real repositories:
Which architecture rule would you want to enforce first?
Top comments (0)