Lint rules don’t just catch mistakes. When stacked deliberately, they form a declarative program that guides agent behaviour through a codebase.
The Observation
Most teams treat ESLint as a quality gate. A set of independent checks that say “you forgot a semicolon” or “this variable is unused.” Each rule fires in isolation. Fix it, move on.
But something different happens when you design rules to depend on each other’s outputs. One rule enforces a structural invariant. When a coding agent satisfies that invariant, it changes the shape of the code. That new shape triggers a different rule. The agent resolves that, which triggers another. The rules form a chain. A pipeline. A program.
You are not writing a linter config. You are writing a program that executes through lint resolution.
Concrete Example: Service Parity Across Surfaces
Consider a product that exposes capabilities through multiple surfaces: an MCP server, a CLI, and a REST API. Every service registered in the core layer should be available on all three surfaces. When a developer (or agent) adds a new service to the core, three things need to happen:
- The MCP server must expose it as a tool
- The CLI must register a command for it
- The REST API must add an endpoint
You can encode this as an ESLint rule. Call it enforce-surface-parity. It reads the service registry, compares it against each surface’s exports, and flags any surface that is missing a service. This rule alone is useful. But it becomes powerful when it interacts with other rules in the stack.
The Chain Reaction
Rule 1: enforce-surface-parity
→ Agent adds MCP tool definition for new service
→ Rule 2: enforce-tool-schema-match
→ Agent adds zod schema matching the service's input type
→ Rule 3: enforce-test-coverage
→ Agent writes integration test for the new MCP tool
→ Rule 4: enforce-docs-for-public-tools
→ Agent adds JSDoc and updates the tool catalogue
Each rule is simple. Each rule fires independently. But the sequence they create is a workflow. The agent walks through it step by step, and at the end you have a fully integrated, tested, documented service surface. Nobody wrote that workflow as imperative code. It emerged from the rule stack.
Why This Works With Agents
Human developers don’t experience lint rules as a chain. They see red squiggles, fix a few, and commit. The ordering is ad-hoc. They often batch fixes or ignore warnings.
Coding agents are different. They operate in a loop: run lint, read violations, fix, re-run lint, repeat. This loop means they naturally execute the rule chain in dependency order. Fix the deepest violation first, and new violations surface as the code changes shape. The agent doesn’t need to understand the full workflow. It just needs to keep resolving violations until the linter is green.
This is the same principle behind constraint propagation in SAT solvers. You don’t tell the solver the answer. You give it constraints. It propagates through them until it finds a satisfying assignment. ESLint rules are constraints. The agent is the solver.
Designing Rule Stacks as Programs
To make this work, you need to think about rule design differently.
1. Rules Must Be Structural, Not Stylistic
Stylistic rules (formatting, naming) don’t chain. They terminate immediately. Structural rules (“every service must have a corresponding test file,” “every public type must have a schema”) change the shape of the codebase when resolved. Shape changes trigger other structural rules.
2. Rules Should Encode Invariants, Not Preferences
Good rule-program rules look like:
- “Every entry in
serviceRegistrymust have a corresponding export inmcp/tools/“ - “Every tool in
mcp/tools/must have a matching zod schema inschemas/“ - “Every schema in
schemas/must have a test in__tests__/“
Bad rules for this purpose:
- “Prefer const over let”
- “Max line length 120”
The first set creates dependency chains. The second set creates dead ends.
3. Order Emerges From Dependencies
You don’t need to specify execution order. The dependency graph between rules creates a natural topological ordering. When the agent fixes a surface-parity violation by adding a new file, the schema-match rule fires on that new file. When the schema is added, the test-coverage rule fires. The order is implicit in the rule design.
4. Rules Can Read Across Boundaries
The most powerful rules in a stack are the ones that read from one part of the codebase and enforce constraints on another. enforce-surface-parity reads the service registry and checks three different surface directories. This cross-cutting validation is exactly what humans forget to do manually and what agents need explicit signals to perform.
The Program Analogy
| Imperative Code | ESLint Rule Stack |
|---|---|
| Function call | Rule violation triggers agent action |
| Control flow | Dependency chain between rules |
| Return value | Shape change in codebase |
| Program counter | Current set of active violations |
| Termination | All rules pass (green lint) |
The “program” executes when the agent runs eslint --fix in a loop. Each iteration is a step. The set of active violations is the program state. Termination is when no violations remain.
Implications for Compound Engineering
This pattern connects to several core principles:
Constraints are the real product. Just like Auto-Harness Synthesis showed that a well-constrained small model beats an unconstrained large model, a well-designed rule stack makes a simple coding agent produce correct, complete work. The rules are the intelligence, not the agent.
Own your control plane. The ESLint config is a control plane for agent behaviour. It encodes your team’s architectural decisions as executable constraints. When you change the rules, you change what agents build. This is owning your control plane applied to code generation.
Infrastructure compounds. One good structural rule prevents a class of errors forever. A stack of ten rules that chain together replaces a workflow document that nobody reads. The rules execute. Documents don’t.
It’s programs all the way down. Agent-Driven Development argues it’s prompts all the way down. This is the complement: it’s constraints all the way down. Prompts say what to build. Lint rules say what “correct” looks like. The agent navigates between the two.
Getting Started
- Audit your existing ESLint rules. Separate structural rules from stylistic ones.
- Identify one invariant that spans multiple parts of your codebase (e.g., “every API route has a test”).
- Write a custom rule that enforces it.
- Run a coding agent against your codebase with that rule enabled. Watch how it resolves violations.
- Add a second rule that fires on the output of the first. You now have a two-step program.
Build from there. The rule stack grows as your architectural invariants grow.
Related:

