@mirzam-ai/lint
v0.5.0
Published
Mirzam Lint — a compiled, opinionated architecture linter. TS-native rule authoring over a Rust engine (Project, AST, shadow-DB SQL oracle).
Readme
Mirzam Lint
Enforce the patterns you need to code with LLMs at scale — without reading all the code. Mirzam Lint compiles your architecture into rules and checks the whole codebase on every run, giving an AI agent (or a human) the same non-negotiable feedback every time: what's allowed, what isn't, and why.
npm i -D @mirzam-ai/lint && npx mirzam-lint setupWhy
Coding agents amplify whatever patterns already exist in a repo — including the
uneven ones. Boundaries with exceptions get copied as exceptions; the codebase
drifts. The usual answer is more prose — CLAUDE.md, AGENTS.md,
.cursorrules — but prose is non-deterministic: the model may read it, may
not, and it rots and bloats as the codebase grows.
"Agents write the code; linters write the law. Agents iterate against deterministic feedback far better than they iterate against vibes."
Mirzam Lint is the harness — it moves the gate into the structure of the codebase, so the rules don't depend on the agent remembering them:
- Deterministic — the same diagnostics every run, with a non-zero exit on any
error, so it gates CI and the agent's own loop. - Whole-codebase, every run — no context window to overflow, no
AGENTS.mdto keep in sync. The rules are the spec. - Diagnostics + intent — every finding states the rule's intent, so the agent reading it learns the pattern, fixes it, and carries it forward. The codebase teaches the next agent.
What it enforces
Far more than boundaries and SQL. Out of the box (tune per repo):
- Architecture — vertical-slice structure, cross-slice import boundaries, the per-slice public surface, folder vocabulary, ownership.
- Function purity — no
Response/framework types in services, no SQL outside repos, I/O only in adapters, routes/services stay pure. - Result & error handling — write paths return
Result<T, TaggedError>; no untyped results; nothrowin repos. - Validation — Zod schemas required at boundaries (
contracts/), contracts must be genuinely cross-slice. - SQL against a real schema — Mirzam Lint builds a shadow database from your
migrations (Postgres or SQLite/D1) and
PREPAREs every query, catching schema drift, missing indexes, and N+1s a type-checker never sees. Unreachable shadow? It says so, loudly — never a silent pass. - Data & tenancy — tier prefixes, bronze immutability, no hard-deletes on
gold, tenant scoping, no optional
org_id. - Time — no bare
new Date()/Date.now(), no ad-hoc date parsing, timestamptz only. - Logging — structured logger only (no raw
console/pino), no console in prod. - React/frontend — no fetch in
useEffect, client fetches need timeouts, no duplicated JSX, component-size limits. - Scale & hygiene — bounded fan-out (no unbounded
Promise.all/spawn), file size limits, duplicate-function detection.
You add codebase-specific rules in TypeScript (below); they're checked the same way, on every run.
Quickstart
npm i -D @mirzam-ai/lint
npx mirzam-lint setup # detect stack/DB/structure → pick a profile →
# write arch-lint.toml → scaffold rules/ → first lint
npm run lint:arch # or: npx mirzam-lintmirzam-lint setup reads your repo and recommends a profile — a curated rule set
for your stack:
| Profile | Stack | Enforces |
|---------|-------|----------|
| node-postgres | TS + Node/Hono + Postgres | VSA slices & boundaries · Zod at boundaries · Result/tagged errors · function purity · structured logging · time discipline · tenancy · SQL drift / index / N+1 vs a Postgres shadow |
| cloudflare-d1 | TS + Cloudflare Workers + D1 | everything above, with SQL checked against a SQLite/D1 shadow + bounded fan-out & fetch-timeout rules for the Workers runtime |
| sanity-nextjs | TS + Next.js + Sanity | VSA slices · Zod · Result/errors · purity · time · logging + React rules (no useEffect fetch, fetch timeouts, no duplicated JSX, component-size) — no SQL |
| generic / portable | any TypeScript / any language | the universal subset; pair with your own rules |
Authoring rules
Portable rules ship in the packs. Codebase-specific rules live with your
codebase as TypeScript — type-checked by your own tsc, scaffolded by
mirzam-lint new-rule:
// rules/no-sql-outside-repos.ts
import { defineRule } from "@mirzam-ai/lint";
export default defineRule({
id: "NO_SQL_OUTSIDE_REPOS",
intent: "raw SQL lives only in repos/ — services compose, repos query",
check(project, ctx) {
return project.files()
.filter((f) => /\bselect\b/i.test(f.text) && !f.relPath.includes("/repos/"))
.map((f) => ({ severity: "error", file: f.path, line: 1,
message: "move SQL into a repo" }));
},
});mirzam-lint new-rule NO_SQL_OUTSIDE_REPOS # scaffolds the rule + passing/violation fixturesEvery rule declares when it fires (scope — e.g. exempt tests), which
variant (typed options), and is toggled per-repo in arch-lint.toml — so
calibration is data, not a fork of the rule.
vs the field
Each capability has prior art; no tool fuses them — and none does it as a compiled engine framed as the loop an agent codes against.
| Tool | Arch boundaries | Migration safety | Query-vs-schema drift | Baseline ratchet | Compiled custom rules | |------|:---:|:---:|:---:|:---:|:---:| | Mirzam Lint | ✅ | ✅ | ✅ | ✅ | ✅ | | ArchUnitTS / Nx / Import-Linter | ✅ | — | — | — | — (interpreted) | | dependency-cruiser | ✅ | — | — | ✅ | — | | Squawk | — | ✅ | — | — | — | | Prisma / Atlas / pg-schema-diff | — | ✅ | — | — | — | | sqlx / sqlc | — | — | ✅ (codegen, single-lang) | — | — | | semgrep / oxlint / Biome / ruff | ✅ (possible) | — | — | — | — (interpreted) |
- Boundary linters are config- or test-runtime-interpreted; none touch SQL.
- The ratchet is commodity — dependency-cruiser ships a known-violations baseline doing exactly "only new violations fail."
- Shadow-DB-from-migrations is commodity too — but Prisma/Atlas/pg-schema-diff
use it for migration diffing, never to
PREPAREapplication queries. PREPARE-every-query-against-a-shadow-schema as a linter is Mirzam Lint's most differentiated mechanism. sqlx/sqlc come closest but are single-language codegen tools (sqlc'sdb-prepareeven routes through its cloud), not standalone architecture linters.- Fast extensible linters can't host it. semgrep, ast-grep, oxlint, Biome (GritQL), ruff, comby all express rules as interpreted patterns — none is compiled/type-checked, and none validates SQL against a schema.
- The agent-harness framing is articulated but unbuilt. Factory.ai states
the identical thesis ("agents write the code; linters write the law") and even
prescribes Mirzam Lint's model — their
eslint-pluginREADME says "NOT to simply import this package... build your own set of custom lint rules tailored to your codebase" (~20 ESLint rules, unmaintained, "for inspiration only"). But that's interpreted ESLint with no SQL oracle, no boundaries, no ratchet — the idea without the engine. Cursor rules / AGENTS.md are prompt-side guidance, not a deterministic build gate. Mirzam Lint is the gate.
Sources: dependency-cruiser, Nx, Import-Linter, ArchUnitTS, sqlx, sqlc, Squawk, Prisma, Atlas, pg-schema-diff, oxlint, semgrep, Biome/GritQL, Factory.ai (primary docs, June 2026).
How it's built
The Rust engine ships as a prebuilt native Node addon: npm i pulls exactly one
@mirzam-ai/lint-<platform> package via optionalDependencies — no Rust
toolchain, no post-install compile, no committed binary. Borrows hard-won
patterns from ruff/oxc (speed), sqlc (DB-checked SQL), and Squawk
(migration safety).
Platform support: macOS (arm64, x64), Linux (x64-gnu, arm64-gnu, x64-musl), and Windows (x64) ship from the CI build matrix. On an unsupported platform the loader tells you.
License
MIT OR Apache-2.0
