Reference
Rule reference
Every rule id and its default severity, generated from the config.
This table is generated from DEFAULT_RULES in packages/core/config.mjs, the same map construct validate reads, so it is always current. Override any severity in architecture.yml:
rules:
PAGE-004: warning
PURE-001: off
Severity is error (validation fails), warning (reported only) or off. Entries marked number are threshold overrides rather than rules. A rule can also take an object with extra options, for example PAGE-007: { severity: error, similarity: 0.9 }.
All rules#
| Rule | Default | What it enforces |
|---|---|---|
ROUTE-001 |
error |
Routes delegate to controllers |
ROUTE-002 |
error |
Routes cannot own business logic or effects |
PAGE-001 |
warning |
Pages hold no state or effect of their own (presentation-only) |
PAGE-002 |
error |
Pages cannot import workflows |
PAGE-003 |
error |
Pages cannot import services |
PAGE-004 |
error |
Pages cannot call fetch |
PAGE-005 |
error |
Pages cannot import domain logic |
PAGE-006 |
error |
Pages cannot use application state/machines |
HOOK-002 |
error |
A hook named use<Name>Provider must be built through defineProvider(...) |
HOOK-001 |
error |
A hook named use<Name>State must be built through useTrackedState(...), with nothing unrelated alongside it |
COMPONENT-001 |
warning |
Components own no application state machine (presentation-only) |
COMPONENT-002 |
error |
Components cannot import controllers |
COMPONENT-003 |
error |
Components cannot import workflows/services/domain |
COMPONENT-005 |
warning |
Components cannot contain inline conditional/loop logic in JSX — must be a named @expression unit |
COMPONENT-006 |
warning |
Component-level JSX complexity budget (nesting depth / inline conditional-or-loop branch count), separate from any one Expression's own cap |
COMPONENT-006-max-depth |
number | Override for COMPONENT-006's max JSX nesting depth |
COMPONENT-006-max-branches |
number | Override for COMPONENT-006's max inline conditional/loop branch count |
PAGE-008 |
warning |
Pages cannot contain inline conditional/loop logic in JSX — must be a named @expression unit |
PAGE-009 |
warning |
Page-level JSX complexity budget (nesting depth / inline conditional-or-loop branch count), separate from any one Expression's own cap |
PAGE-009-max-depth |
number | Override for PAGE-009's max JSX nesting depth |
PAGE-009-max-branches |
number | Override for PAGE-009's max inline conditional/loop branch count |
EXPR-001 |
error |
Expressions are pure (no side effects) |
EXPR-002 |
warning |
An Expression's own JSX complexity budget (nesting depth / inline conditional-or-loop branch count) |
EXPR-002-max-depth |
number | Override for EXPR-002's max JSX nesting depth |
EXPR-002-max-branches |
number | Override for EXPR-002's max inline conditional/loop branch count |
EXPR-003 |
warning |
Expressions need an unambiguous, non-trivial name — not the bare name of their control-flow kind |
EXPR-004 |
error |
Expressions cannot contain hand-authored JSX beyond wrapping/passthrough |
EXPR-005 |
error |
Expressions must accept children and return JSX |
EXPR-006 |
error |
Expressions must be built through defineExpression(...) (the shared Template<Props> type) |
WORKFLOW-001 |
error |
Workflows cannot import React/UI |
WORKFLOW-002 |
warning |
Workflow states must be reachable from the initial state |
WORKFLOW-003 |
warning |
Non-final workflow states must have a way out |
WORKFLOW-004 |
off |
Every non-final workflow state must decide every event the machine handles (transition or explicit ignore) |
STATE-001 |
off |
Workflow/hook state is a discriminated union on one status field, not a bag of co-occurring loading/error/data flags |
CONTROLLER-001 |
error |
Controllers must compose (import + wire only) — no business logic or raw fetch() |
PAGE-007 |
warning |
Pages wrap frozen (externally-authored) markup instead of reimplementing it |
COMPONENT-004 |
warning |
Components wrap frozen (externally-authored) markup instead of reimplementing it |
CONTROLLER-002 |
warning |
Controllers wrap frozen (externally-authored) markup instead of reimplementing it |
SERVICE-001 |
error |
Services own external effects |
SERVICE-002 |
error |
Services cannot import React/UI |
SERVICE-003 |
off |
Services forward the caller's AbortSignal to fetch() so a superseded request never lands |
CONTROLLER-003 |
off |
A controller holds no state of its own (no useState/useRef/useEffect/useMemo/useCallback), so it binds the hook's live value, never a stale copy |
HOOK-003 |
off |
A useEffect that starts a listener, timer, subscription or request returns its cleanup, so it never outlives its hook |
ROUTE-003 |
off |
A route forwards params/searchParams whole instead of reading them, so a raw URL string never travels inward |
DOMAIN-001 |
error |
Domain is pure |
DOMAIN-002 |
off |
Domain code may only reference its own parameters/local bindings, type-only imports, and a small set of JS built-ins (allowlist, not denylist) |
CLIENT-001 |
off |
A 'use client' file cannot import server-only code |
SLICE-001 |
error |
Feature internals are isolated |
SLICE-002 |
error |
Cross-feature imports use public index.ts |
MODULE-001 |
error |
One primary module per file |
PURE-001 |
warning |
Domain functions should be deterministic (no Math.random, Date.now, new Date(), performance.now, crypto id) |
DRY-001 |
warning |
Business knowledge has one source of truth |
SOC-001 |
error |
Every responsibility has an architectural owner |
SLICE-003 |
warning |
Public API (index.ts) stays in sync with actual feature exports |
SLICE-004 |
error |
Cross-feature component/provider re-exports must be a distinct wrapper |
READ-001 |
error |
Components/controllers are PascalCase; hooks are use-prefixed camelCase |
READ-002 |
error |
Files and functions stay under their length threshold |
READ-003 |
warning |
Public API exports document intent with JSDoc |
READ-002-max-loc |
number | Override for READ-002's max-lines-per-file threshold |
READ-004 |
off |
A unit's filename encodes its layer as a suffix (Name.layer.ext) |
TYPE-001 |
off |
Code must type-check (a real tsc --noEmit run with the project's own TypeScript) |
NAME-001 |
off |
A unit's file path and exported symbol must match layerTargetFile/layerFileBaseName's derivation for its own name |
TYPE-002 |
off |
An exported function's parameter and return value must each carry an explicit type annotation (no implicit any at a unit boundary) |
IMPORT-001 |
error |
Relative imports must resolve to a file that exists |
SLOT-001 |
error |
A generated file's slot region must match its member's stored slot body in the map |
SLOT-002 |
error |
A slot body may only import types, never a value |
EXCEPTION-EXPIRED |
warning |
Time-boxed exceptions must be renewed or removed once they expire |
PROP-LINK |
info |
A required prop is never passed at some call site, or a call site passes an undeclared prop |
PARSE-ERROR |
error |
A file must be syntactically valid TypeScript/JSX to be checked |
To add a rule, see Extending Construct.