Every team using coding agents eventually ends up with a growing CLAUDE.md or AGENTS.md.
"Inject a
Clock, never callnow()directly."
"Fetch data through query composables, never callfetch()in a component."
"Log messages start lowercase."
And twenty more instructions, each added after an agent got something wrong.
The file keeps growing, and every line eats into a limited context window. So you split it into folders and linked docs. Now less gets loaded, but a doc the agent never opens is a rule it breaks.
The agent keeps making the same mistakes.
There's a better split: prose for what only prose can say (business logic, domain knowledge, the why), lint rules for everything a machine can check. A lint rule stays silent while the agent gets it right. When it doesn't, the feedback loop points to the exact line within seconds, and the agent fixes it on its own.
What that looks like
Take the Clock rule. In CLAUDE.md, it's a sentence the agent may or may not follow:
- Always inject a `Clock`. Never call `LocalDateTime.now()` or `Instant.now()` directly. As an ast-grep rule, it's a check that runs on every edit:
id: inject-clock language: kotlin severity: error message: Don't call now() directly. Inject a Clock and use now(clock). rule: pattern: $TYPE.now() When the agent writes Instant.now(), it gets feedback like this a few seconds later, while it's still working on the file:
error[inject-clock]: Don't call now() directly. Inject a Clock and use now(clock). src/main/kotlin/.../InvoiceService.kt:42 It fixes the code before it ever reaches review. The CLAUDE.md line shrinks to a one-line pointer, or disappears entirely.
What I measured
I ran about 500 headless Claude Code sessions on four real projects in Kotlin, Java, Python and TypeScript. Each run compared a rule written as prose with the same rule as a lint rule plus a feedback loop.
- Prose the agent can miss (a linked doc, or one line in long docs) was broken regularly. As a lint rule, it was broken in none.
- The lint rule was 14–22% cheaper, because no run needed a review round to fix a break.
- Simple, mechanical rules need no prose at all. The lint rule alone is enough.
- Prose in the root CLAUDE.md is followed too, but it costs tokens in every session, whether the task needs it or not.
Methodology, data and harness to rerun it on your own repo: agent-rules-bench.
Meet codemyrules
codemyrules is an agent skill that turns your existing decisions, coding conventions, and agent instructions into executable lint rules.
It works with the knowledge your team already has:
- Discovers conventions already expressed in your code, decision records (ADRs), and agent docs.
- Lets you choose what to enforce. Nothing is built until you pick it.
- Generates lint rules with tests, using ESLint for JavaScript, TypeScript, Vue and Svelte, and ast-grep for Java, Kotlin, Python, Go and more.
- Cleans up your agent docs by offering to delete each instruction a rule now enforces, or shorten it to a one-line pointer.
- Closes the feedback loop. The agent sees a lint violation seconds after writing the code, rather than days later in review.
Nothing is changed without your approval. The feedback loop is private by default: it installs into .claude/settings.local.json and is git-excluded, so no tracked file changes until you decide to share it with your team.
The goal isn't to replace your CLAUDE.md or AGENTS.md with a giant collection of lint rules. It's to stop using prose for conventions a machine can check, and keep your docs for the context only prose can give.
Where the rule goes
The skill picks the first option that fits:
- An existing check. A rule in a linter you already run (ESLint and its plugins, Ruff, detekt, golangci-lint, Error Prone, analyzers) or an architecture test library you already use (ArchUnit, Konsist, import-linter, Deptrac). You get its exact config, even for rules that exist but are switched off.
- Your own rule set. If the project already maintains custom rules (a local ESLint plugin, a detekt rule set, Error Prone checks, a flake8 plugin), the new rule goes there, in the same style, with the same tests and registration.
- A new rule. For frontend, a custom ESLint rule with RuleTester tests. For backend, one ast-grep YAML rule and one YAML test, registered in
sgconfig.yml.
What it doesn't do
Not every convention is a lint rule. "Keep services small and focused" or "name things after the domain, not the framework" need judgment, and they stay in your docs. codemyrules is for the rules a pattern match can catch.
It also won't turn your CI red on day one. Rules your existing code already breaks go into a separate suggestions layer, so they guard new code without forcing you to fix legacy code first. And the feedback loop only blocks on lines the agent changed, so it never drags the agent into unrelated cleanup.
Harness support
The generated lint rules work with any coding agent or harness (the tool that runs the agent): Claude Code, Codex, Copilot, Cursor and others.
The feedback loop is built on Claude Code hooks, so for now it's only offered in Claude Code. I'm working on bringing it to Codex, pi, OpenCode, and other coding-agent environments.
Try it
In Claude Code, install the plugin:
claude plugin marketplace add clirimajeti/codemyrules claude plugin install codemyrules@codemyrules codemyrules drives one sub-skill per stack, so add the ones you need: eslint-pattern-rules for frontend and backend-pattern-rules for backend. The README has the commands, plus setup for other agents.
Then ask your agent:
Turn our ADRs and AGENTS.md into lint rules. Feedback welcome
This is the first public release, and I'd love to see what happens when you run it against a real codebase.
Tell me what it found, what it missed, and how much of your CLAUDE.md turned out to be lint rules in disguise.
Comments here and issues on GitHub are both welcome.
