Brain index

INSIGHT 18: Custom Lint Rules Are Executable Architecture

Some architecture rules are too important to leave as prose. If the policy is mechanically detectable, a lint/static-analysis rule gives the agent a concrete failure to repair: file, span, rule ID, evidence, and expected direction.

This note is not a claim that every design judgment should become a lint rule. It is narrower: repo-specific constraints that agents repeatedly violate should move from "remember this convention" to executable feedback where possible.

Plot-ready data for nearby claims lives in presentations/write-code-ai-agents-love/research/data/names_types_apis.csv and presentations/write-code-ai-agents-love/research/data/feature_constraints_planning.csv.

Source map

RefSourceLocal textRole in this insight
D13ESLint custom rulesarticles/eslint-custom-rules.htmlMainstream proof that teams can encode project-specific JS/TS policy when stock rules do not cover it.
D14typescript-eslint custom rulesarticles/typescript-eslint-custom-rules.htmlType-aware rule authoring path for richer TypeScript constraints.
D15Semgrep custom guardrailsarticles/semgrep-custom-guardrails.htmlPractitioner pattern: organization-specific rules in IDE, PR, and pre-commit flows.
D16Semgrep rule ideasarticles/semgrep-rule-ideas.htmlShows prose guidance can often be turned into authentication, validation, or security rules.
D17ast-grep lint rulearticles/ast-grep-lint-rule.htmlAST-pattern rules with globs, messages, severity, and fixes.
D18Nx enforce module boundariesarticles/nx-enforce-module-boundaries.htmlMature architecture-boundary lint pattern in monorepos.
D31polint READMEarticles/polint-readme.mdLocal option: repo-owned rule code with scan/fact/diagnostic infrastructure.
D32polint Agent Playbookarticles/polint-agent-playbook.mdAgent-facing JSON output, focused remediation, baselines, ignores, and loop commands.
D33polint ignore commentsarticles/polint-ignore-comments.mdSuppression/debt tracking for gradual adoption.
R18Evaluating AGENTS.mdpaper-text/evaluating-agents-md-2602.11988.txtCounterweight: context/prose files can add cost and noise.
R59Smells of LLM Generated Codepaper-text/smells-llm-generated-code-2510.03029.txtStatic quality signals matter because LLM code can carry more smell risk.
R60Causal Smellspaper-text/causal-smells-llm-code-2511.15817.txtStatic smell signals are measurable but need careful interpretation.
Localpolint/plint case studynotes/polint-plint-case-study.mdReal local example of architecture policy as code.

Why prose is the weak form of architecture

An instruction file can say "UI code must not import persistence modules." That is useful orientation, but it is weak enforcement. The agent has to remember it, recognize the relevant modules, notice that an import crosses the boundary, and then choose the right repair. If the code compiles and tests pass, nothing forces the architecture issue back into the loop.

A static diagnostic changes the shape of the interaction. The rule can point at the exact import, name the violated boundary, include the owning package or layer, and suggest the approved path. This is why lint rules are not just "quality bureaucracy" in an agentic codebase. They are part of the agent's computer interface.

That distinction matters because AGENTS.md-style prose has mixed evidence. One instruction-file study reports efficiency benefits, but Evaluating AGENTS.md finds that context files can increase steps and cost and that generated context can reduce success. The synthesis is not "prose bad." It is: prose should index the rule; executable tools should enforce the rule when the rule is mechanical.

What mainstream tooling already proves

The custom-rule ecosystem matters because it prevents this insight from turning into a polint pitch. The pattern is broader than any one tool.

Tool familyWhat it makes executableWhy it matters for agents
ESLint custom rulesJavaScript/TypeScript AST patterns and project conventionsAgents get exact file/span failures in a tool they already run.
typescript-eslintType-aware lint rules through TypeScript parser/checker servicesRules can reason about symbols and types, not just syntax.
SemgrepOrganization-specific security and correctness guardrailsProse security guidance can become IDE/PR/pre-commit feedback.
ast-grepStructural AST search, lint, and rewrite rulesUseful when the check is a syntactic pattern plus optional fix.
Nx module boundariesDependency constraints between tagged projects/packagesArchitecture layering becomes a dependency graph rule.
dependency-cruiser / similarImport graph policiesAgents cannot silently cross forbidden package/layer edges.

This tooling evidence is not causal agent-performance evidence. It is feasibility evidence: the industry already accepts executable local policy as a normal way to keep project conventions alive.

Where custom lint is actually worth it

The high-value rules are not formatting preferences. They are the conventions that keep a codebase understandable and safe while agents make fast multi-file edits.

Policy familyExample ruleNeeded facts
Architecture boundariesapps/web cannot import backend/internal/persistencepaths, imports, resolved module graph
Public API disciplinepackages must import through public exports, not deep internalsimports, package exports, symbol resolution
Generated code policydo not edit generated SDK output by handpaths, generated markers, CI generation check
API integrationinternal HTTP calls must use generated clientsimports, call expressions, string literals, type facts
Security middlewaremutating routes require CSRF/auth middlewareroute registration syntax, call chains, path metadata
Validation-before-useexternal input must pass schema validation before persistencedataflow, call graph, validator facts
Context propagationrequest context must flow into database/service callsfunction signatures, call graph, dataflow
Test evidencenew routes/branches need matching component/unit testsroutes, symbols, test facts, coverage or naming conventions

The table also shows why simple YAML pattern matching is sometimes enough and sometimes not. A forbidden import can be a pattern. Context propagation or test evidence usually needs computed facts, repo-specific path semantics, and exceptions.

The polint/plint case study: useful, but not the whole story

polint is a local implementation option for "bring your own rules." It should be presented as one tool in the executable-architecture toolbox, not as the universal answer.

The important product shape:

  • the consumer repo owns the rule pack;
  • the rules are normal Rust code under .polint/rules;
  • polint supplies scanning infrastructure, typed facts, diagnostics, config, cache, JSON/SARIF, baselines, and ignores;
  • agents can run focused checks and parse stable machine output.

The real plint usage is a strong case study because the rules are not toy style checks. The local notes list 17 warning-level rules during adoption:

Rule areaExamples from plint notes
Layer importsbackend-layer-imports, app/domain/adapter purity checks
Route securitymutating routes require CSRF; active-school routes require guard
Context propagationbackend context must flow through service/repository calls
Typed errorsadapter/domain typed error discipline
UUID boundariesUUID usage stays at boundary layers
Repository placementrepository interfaces in expected locations
Test evidenceHTTP route component tests, GORM adapter coverage, domain tests

This is the talk-safe phrasing:

For ordinary lint, use ordinary lint. When the rule is your repo's architecture encoded in code, a code-based local rule pack is a useful escape hatch.

Why this connects to the research data

The custom-lint claim is partly backed by direct tooling evidence and partly by indirect agent research.

CODETASTE uses static rules as part of the benchmark oracle for refactoring intent. That matters because it separates "tests pass" from "the intended structural transformation happened." Needle in the Repo shows behaviorally correct outputs can still be structurally wrong. The LLM-smell papers show that generated code can carry higher maintainability-risk signals than professional references.

The static-rule platform is the implementation path for those findings:

  • if dependency control is hard, encode dependency rules;
  • if generated SDK usage matters, ban raw internal API calls;
  • if route security is repeatedly missed, fail the missing middleware;
  • if a migration has additive and reductive patterns, encode both;
  • if smells are acceptable only under certain local exceptions, put the exceptions in tested rule code instead of tribal memory.

Caveats and non-claims

This does not prove that custom lint rules improve agent performance by a known percentage. I do not have that direct experiment. The claim is an inference from three things: agents need repairable feedback, static oracles catch structural failures, and existing tooling can encode repo-specific rules.

Bad lint rules are worse than stale docs because they block work. Every custom rule needs valid/invalid tests, an owner, documented precision, known false positives, a baseline/ignore strategy, and a clear removal/update path.

Do not lint nuanced design judgment. Lint mechanical constraints: imports, generated-code boundaries, required middleware, banned APIs, validation paths, test-evidence conventions, migration completeness, and other facts a tool can reason about.

Do not make the final article sound like "install my tool." The stronger argument is tool-agnostic:

Do not ask the agent to remember your architecture. Make the mechanically checkable parts of the architecture fail precisely.

Blog visual candidates

  1. Prose instruction vs static diagnostic: same architecture rule, two feedback loops.
  2. Tooling ladder: stock lint -> custom ESLint/Semgrep/ast-grep -> repo-local rule code -> richer fact model.
  3. plint rule taxonomy table: boundaries, security, context propagation, typed errors, test evidence.
  4. "Bad custom lint" caveat box: no owner, no tests, no baseline, vague message.
  5. Rule lifecycle: convention -> repeated agent failure -> rule prototype -> warning + baseline -> error on new violations.

References

  • D13: ESLint custom rules, articles/eslint-custom-rules.html
  • D14: typescript-eslint custom rules, articles/typescript-eslint-custom-rules.html
  • D15: Semgrep custom guardrails, articles/semgrep-custom-guardrails.html
  • D16: Semgrep rule ideas, articles/semgrep-rule-ideas.html
  • D17: ast-grep lint rule, articles/ast-grep-lint-rule.html
  • D18: Nx enforce module boundaries, articles/nx-enforce-module-boundaries.html
  • D31: polint README, articles/polint-readme.md
  • D32: polint Agent Playbook, articles/polint-agent-playbook.md
  • D33: polint Ignore Comments, articles/polint-ignore-comments.md
  • R18: Evaluating AGENTS.md, paper-text/evaluating-agents-md-2602.11988.txt
  • R59: Smells of LLM Generated Code, paper-text/smells-llm-generated-code-2510.03029.txt
  • R60: Causal smell analysis, paper-text/causal-smells-llm-code-2511.15817.txt
  • Local case study: notes/polint-plint-case-study.md