Lint Guardrails
Guardrails make agent-written code readable by construction (based on
"ESLint as AI Guardrails"):
size/complexity caps that force extraction over sprawl, no comments
(explanations go to docs), named constants. The philosophy is
ecosystem-agnostic; implementations live in per-ecosystem support files.
The philosophy
- Everything is an error, never a warning.
- Never raise a cap or add a suppression to make code fit — hitting
a limit means extract, split, or rename.
- Comments are banned; knowledge lives in docs, names, and test titles.
- The config is protected: a PreToolUse deny hook blocks agent edits
to the lint config. Timing and caveats live with the implementation.
- Enforced at a commit gate, not per edit — the guardrails only bite
if lint runs before code lands: wire it into a git-hook gate backed by
CI (the un-bypassable backstop), never a per-edit agent hook that fires
mid-refactor. The wiring is ecosystem-specific.
- Vendored/generated code, tests, scripts, and root config files get
scoped exemptions — correctness rules stay on everywhere.
Implementations
- JS/TS (ESLint) — ESLINT.md: the rules, deviations
learned in practice, scoped exemptions, the protect hook, and
remediation specifics.
- Other ecosystems — apply the philosophy with the native tools
(e.g. clippy caps for Rust); add a support file here once an
implementation stabilizes in real use.
Remediating an existing codebase
On a fresh scaffold there is nothing to fix. On an existing repo:
- Fix pre-existing lint errors before adding rules — never mix the two.
- Measure, group by rule and by file, and report the blast radius to
the user before starting — it can be hours.
- Comments: first handle functional ones manually (suppressions,
license headers). Harvest every comment with file:line to a scratch
file before stripping the rest. Relocate load-bearing knowledge from
the harvest: domain vocabulary → glossary; decisions/invariants →
ADRs; subsystem warnings → a README next to the code; env-var
semantics →
.env.example; test intent → describe/it names.
Everything else: better names.
- Fix cheapest-first — the per-ecosystem file has the exact order and
patterns.
- Run the test suite after each batch; runtime-verify UI at the end if
components were restructured.
- Write
docs/agents/linting.md (rules table, deviations, exemptions,
where explanations go, the protect hook) and point AGENTS.md /
CLAUDE.md at it with the one-liner: "When a cap fires, extract —
never raise the cap." Then install the protect hook as the final
lint action.
Don't remediate on a dirty tree; the diff must stay reviewable.
If jq (needed by the protect hook) is missing, prompt the user with
its install command from skill.deps.json.
1---2name: lint-guardrails3description: AI-guardrail linting — size/complexity caps that force extraction over sprawl, no comments, named constants, everything an error, and the config protected from agent edits by a deny hook. Ecosystem-agnostic philosophy with per-ecosystem implementations (ESLint for JS/TS today). Standalone-safe on existing repos. Use when the user says "setup linting", "add lint guardrails", or via setup-tooling.4---56# Lint Guardrails78Guardrails make agent-written code readable by construction (based on9["ESLint as AI Guardrails"](https://medium.com/@albro/eslint-as-ai-guardrails-the-rules-that-make-ai-code-readable-8899c71d3446)):10size/complexity caps that force extraction over sprawl, no comments11(explanations go to docs), named constants. The philosophy is12ecosystem-agnostic; implementations live in per-ecosystem support files.1314## The philosophy1516- Everything is an **error**, never a warning.17- **Never raise a cap or add a suppression to make code fit** — hitting18 a limit means extract, split, or rename.19- Comments are banned; knowledge lives in docs, names, and test titles.20- The config is **protected**: a PreToolUse deny hook blocks agent edits21 to the lint config. Timing and caveats live with the implementation.22- **Enforced at a commit gate, not per edit** — the guardrails only bite23 if lint runs before code lands: wire it into a git-hook gate backed by24 CI (the un-bypassable backstop), never a per-edit agent hook that fires25 mid-refactor. The wiring is ecosystem-specific.26- Vendored/generated code, tests, scripts, and root config files get27 scoped exemptions — correctness rules stay on everywhere.2829## Implementations3031- **JS/TS (ESLint)** — [ESLINT.md](./ESLINT.md): the rules, deviations32 learned in practice, scoped exemptions, the protect hook, and33 remediation specifics.34- **Other ecosystems** — apply the philosophy with the native tools35 (e.g. clippy caps for Rust); add a support file here once an36 implementation stabilizes in real use.3738## Remediating an existing codebase3940On a fresh scaffold there is nothing to fix. On an existing repo:41421. Fix pre-existing lint errors before adding rules — never mix the two.432. Measure, group by rule and by file, and report the blast radius to44 the user before starting — it can be hours.453. Comments: first handle functional ones manually (suppressions,46 license headers). Harvest every comment with file:line to a scratch47 file before stripping the rest. Relocate load-bearing knowledge from48 the harvest: domain vocabulary → glossary; decisions/invariants →49 ADRs; subsystem warnings → a README next to the code; env-var50 semantics → `.env.example`; test intent → describe/it names.51 Everything else: better names.524. Fix cheapest-first — the per-ecosystem file has the exact order and53 patterns.545. Run the test suite after each batch; runtime-verify UI at the end if55 components were restructured.566. Write `docs/agents/linting.md` (rules table, deviations, exemptions,57 where explanations go, the protect hook) and point AGENTS.md /58 CLAUDE.md at it with the one-liner: "When a cap fires, extract —59 never raise the cap." Then install the protect hook as the final60 lint action.6162Don't remediate on a dirty tree; the diff must stay reviewable.6364If `jq` (needed by the protect hook) is missing, prompt the user with65its install command from [skill.deps.json](./skill.deps.json).