docs/rules/writing

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.

last updated · apr 18, 2026·8 min readedit on github

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.

.driftlog.yaml
rules:
- type: layer
name: "app layers"
scope: src/**
severity: error
ignore:
- generated/**
- "**/*.test.ts"

Layer

type: layer

Top-to-bottom only. Each layer can import from itself and anything below; never from anything above. The list order is the truth.

.driftlog.yaml
rules:
- type: layer
order:
- ui/**
- domain/**
- infra/**
severity: error
triggers
Any import that crosses upward. Domain importing ui, infra importing domain, that sort of thing.
error ui/Cart.tsx:14
layers.app ui imports infra (@/infra/stripe)

Boundary

type: boundary

A peer-to-peer wall. Sibling modules can see the world, but not each other, except through what you list in allow_through.

.driftlog.yaml
rules:
- type: boundary
name: billing.catalog
modules:
- billing/**
- catalog/**
allow_through: shared/contracts/**
severity: error
triggers
Any direct import between two listed modules that does not go through allow_through.
error catalog/index.ts:3
boundary.billing.catalog direct import billing/api

Forbidden import

type: forbidden

A 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.

.driftlog.yaml
rules:
- type: forbidden
from: src/**
import:
- lodash
- moment
reason: "Use the native stdlib."
severity: warn
triggers
Any import resolving to a name in the import list, anywhere matched by from.
warn src/utils/dates.ts:1
forbidden import 'moment' (Use the native stdlib.)

Cycles

type: cycles

Module-level import cycles. Set max_length to 0 to fail on any cycle; set higher numbers if you accept short ones during a migration.

.driftlog.yaml
rules:
- type: cycles
scope: src/**
max_length: 0
severity: error
triggers
A directed cycle in the module import graph longer than max_length.
error cycles.src
2 hops: billing/ -> catalog/ -> billing/

Custom matchers

type: matcher

Glob aliases. Define them once, reuse them across rules. They keep the rule body honest by not repeating the same path expression in five places.

.driftlog.yaml
# Match any file in src/api whose name ends in -client.ts
matchers:
api_clients: "src/api/**/*-client.ts"
 
rules:
- type: forbidden
from: ui/**
import: api_clients
reason: "UI must talk to api via hooks, not raw clients."
severity: error
triggers
Any forbidden / boundary rule that references the matcher name.

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.

exit codes
exit 0All rules passed (or only warnings).
exit 1At least one error-severity violation.
exit 2Configuration error. .driftlog.yaml unparseable.
exit 127Internal error. Open a GitHub issue.
Rules — Driftlog Docs