Navigation

Enforcement Ladder

A convention that holds only while someone remembers it is a bug with a delay. Every rule this codebase keeps is placed on the highest rung that can carry it, and each rung down costs more for as long as the rule exists. The ladder is how "we should always do X" becomes something the build proves, so the codebase keeps getting better without anyone re-reading every file.

The rungs

RungWhat holds the ruleWhat it costs forever
1. By constructionThe wrong version does not typecheck or cannot be expressedNothing
2. StructuralOne primitive does the job, and every caller goes through itOne review of the primitive
3. LintA stock rule, a no-restricted-syntax selector, or a custom oxlint plugin fails itNothing per file: one pass covers the whole monorepo, and new files are covered
4. TestA test of the primitive itself, or a sweep script under scripts/src/sweeps/, fails itUpkeep: fixtures and assertions move with the code they pin
5. RememberedA rule in the owning skill and the owning docs page, read by review and sweepsEvery reader, on every change, forever

Lint sits above a test because it needs nothing when the repo changes: a new file is linted the moment it exists, where a test covers only the paths someone wrote it for. A test sits above prose because it fails on its own, where prose is only as good as the last person who read it.

Choosing a rung

flowchart TD
  R["A rule that must hold in many places"] --> C{"Can the types make the wrong version unwritable?"}
  C -->|yes| T1["1. By construction — change the shape"]
  C -->|no| P{"Can one primitive own the job, so callers only pass data?"}
  P -->|yes| T2["2. Structural — build the primitive, route every caller through it"]
  P -->|no| L{"Visible in one file's syntax, with no growing exception list?"}
  T2 --> L
  L -->|yes| T3["3. Lint — stock rule, then a selector, then a plugin"]
  L -->|no| X{"Can a test fail on the wrong version — the primitive's unit test, or a whole-repo scan?"}
  T3 --> X
  X -->|yes| T4["4. Test"]
  X -->|no| T5["5. Remembered — the owning skill and docs page"]
  T4 --> W["Every rung: the why lives in its owning skill or page"]
  T5 --> W

The rungs stack rather than replace each other. A primitive is usually backed by a lint rule that bans the old way of doing its job, and by a unit test on the primitive itself; what lint and tests cannot state — why the rule exists, and which shapes are its honest exceptions — is written once, in the skill or page that owns it, so a disable comment has something to name.

What keeps the ladder honest

  • A missing check is a finding about the design. When a bug is one call site missing its guard, the fix is the shape that makes the guard unforgettable, landed in the same change.
  • No roster. A lint rule whose exceptions are a list of paths, helper names or suffixes goes wrong the first time the repo grows without an edit to it, so a rule that can only be stated that way stays on a lower rung.
  • A finding written twice is handed to an enforcer. A review or a sweep that reports the same class of problem in two places has shown the rule cannot survive on being remembered.
  • An enforcer goes with what it guarded. Deleting a shape deletes its lint rule or test in the same change, since each one costs time on every CI run.

A worked example

Nested interactions climbed the ladder in one change. A link in a todo's notes opened a new tab and the todo's dialog, because each clickable row relied on @click.stop walls someone had to remember around each control — rung 5, and a wall can never reach a link inside rendered HTML. The fix is structural: one guard, checkIsNestedInteraction, asked by the table row and the drag-aware click composable, so every control inside is covered without being named. Lint bans the bare @click.stop wall so the old way cannot come back, and unit tests pin the guard and the table's wiring. Only the why is prose.

Key files

FileRole
.agents/skills/invariants/SKILL.mdThe ladder as the rule a session follows
.agents/skills/oxlint/references/custom-js-plugins.mdWhen a lint rule earns a custom plugin, and the roster gate
.agents/skills/sweeps/references/handing-to-an-enforcer.mdTurning a repeated finding into an enforcer
packages/configuration/eslint/The no-restricted-syntax lists, each with its fixture suite
scripts/src/oxlint/The custom oxlint plugins
scripts/src/sweeps/Whole-repo scans for what one file's syntax cannot decide

Details

Command palette

Keyboard shortcuts