@dowel/dowel
v0.3.1
Published
dowel — a compiled, opinionated architecture linter. TS-native rule authoring over a Rust engine (Project, AST, shadow-DB SQL oracle).
Downloads
1,374
Readme
dowel
A deterministic harness for coding agents. dowel compiles your architecture into rules and checks the whole codebase on every run, so an AI agent — or a human — gets the same non-negotiable feedback every time: what's allowed, what isn't, and why.
npm i -D @dowel/dowel && npx dowel 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 as the codebase grows.
"Agents write the code; linters write the law. Agents iterate against deterministic feedback far better than they iterate against vibes."
dowel moves the gate into the structure of the codebase. Architecture becomes compiled, checkable law:
- Deterministic — the same diagnostics every run, not a suggestion the model
weighs. Exit code is non-zero on any
error, so it gates CI and agent loops. - Whole-codebase, every time — 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 and fixes it, then carries the pattern forward. The codebase teaches the next agent.
What it checks that a type-checker can't
- Architecture boundaries — vertical-slice structure, cross-slice imports,
layer purity (no SQL in services, no
Responsein repos, Result at write boundaries), folder vocabulary, ownership. - Raw SQL against a real schema — dowel builds a shadow database from
your migrations (Postgres or SQLite/D1) and
PREPAREs every query against it, catching drift, missing indexes, and N+1s a type-checker never sees. If the shadow is unreachable it says so, loudly — never a silent pass.
Quickstart
npm i -D @dowel/dowel
npx dowel setup # detects stack/DB/structure, picks a profile,
# writes arch-lint.toml, scaffolds rules/
npm run lint:arch # or: npx doweldowel setup reads your repo and recommends a profile — a curated rule set
for your stack:
| Profile | For |
|---------|-----|
| node-postgres | Node/Hono + Postgres |
| cloudflare-d1 | Cloudflare Workers + D1 (SQLite) |
| sanity-nextjs | Next.js + Sanity (no SQL) |
| generic / portable | any TypeScript / any language baseline |
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
dowel new-rule:
// rules/no-sql-outside-repos.ts
import { defineRule } from "@dowel/dowel";
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" }));
},
});dowel 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.
How it's built
The Rust engine ships as a prebuilt native Node addon: npm i pulls exactly one
@dowel/dowel-<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:
darwin-arm64ships today; the Linux/Windows binaries land via the CI build matrix. On an unsupported platform the loader tells you.
License
MIT OR Apache-2.0
