Rules
A Driftlog rule is a file. Four built-in shapes cover everything most teams need: layers, boundaries, forbidden imports, and cycles. Anything else is a custom matcher.
Every rule has the same shape: a type, a scope (where the rule applies), a severity (error or warn), and an ignore list. The body fields differ per type.
Rule anatomy
The shared frame. Use scope to limit a rule to part of the repo, and ignore to peel off generated or vendored files.
rules:- type: layername: "app layers"scope: src/**severity: errorignore:- generated/**- "**/*.test.ts"
Layer
type: layerTop-to-bottom only. Each layer can import from itself and anything below; never from anything above. The list order is the truth.
rules:- type: layerorder:- ui/**- domain/**- infra/**severity: error
error ui/Cart.tsx:14layers.app ui imports infra (@/infra/stripe)
Boundary
type: boundaryA peer-to-peer wall. Sibling modules can see the world, but not each other, except through what you list in allow_through.
rules:- type: boundaryname: billing.catalogmodules:- billing/**- catalog/**allow_through: shared/contracts/**severity: error
error catalog/index.ts:3boundary.billing.catalog direct import billing/api
Forbidden import
type: forbiddenA blocklist with a reason. The reason text shows up in CI annotations and PR comments, so it pays to write it like you mean it.
rules:- type: forbiddenfrom: src/**import:- lodash- momentreason: "Use the native stdlib."severity: warn
warn src/utils/dates.ts:1forbidden import 'moment' (Use the native stdlib.)
Cycles
type: cyclesModule-level import cycles. Set max_length to 0 to fail on any cycle; set higher numbers if you accept short ones during a migration.
rules:- type: cyclesscope: src/**max_length: 0severity: error
error cycles.src2 hops: billing/ -> catalog/ -> billing/
Custom matchers
type: matcherGlob aliases. Define them once, reuse them across rules. They keep the rule body honest by not repeating the same path expression in five places.
# Match any file in src/api whose name ends in -client.tsmatchers:api_clients: "src/api/**/*-client.ts"rules:- type: forbiddenfrom: ui/**import: api_clientsreason: "UI must talk to api via hooks, not raw clients."severity: error
Rule testing
When you author or tune a rule, run it in isolation to see what it fires on. driftlog rule test <id> traces a single rule against the current working tree with verbose timing — see /cli#rule for the full flag reference.
Iterate by toggling --rule <id> on driftlog check and re-running. The check command's full violation output gives you context the per-rule tracer doesn't: severity bands, suppression hits, and which scope the rule resolved on each file.
During a migration window, run driftlog check --strict in CI to promote warnings to errors. That turns the rule's accumulated noise into a hard fail without changing the rule's declared severity — useful when you want a deadline pressure on cleanup without losing the ability to roll the rule back to warn-mode.
Severity and exit codes
Every rule sets severity: error or severity: warn. Errors fail the run (exit 1); warnings are reported and never block. Use --strict in CI to promote warnings to errors during a migration window.