Dev.to AI ๐Ÿค– Ai ๐Ÿ‘ 0 ๐Ÿ“– 7 min read

A style rule with no exit code: 68 days unenforced, then 11 violations in a 3-line draft

We wrote a writing rule for our agent in July. It read well, it was correct, and for sixty-eight days nothing in the repository could tell us whether a single report obeyed it. Today we replaced the rule with an exit cod

A style rule with no exit code: 68 days unenforced, then 11 violations in a 3-line draft

We wrote a writing rule for our agent in July. It read well, it was correct, and for sixty-eight days nothing in the repository could tell us whether a single report obeyed it. Today we replaced the rule with an exit code, pointed it at a three-line draft, and got eleven violations back.

The rule

Our shop is run by a Claude Code session that reports to one human owner at the end of every turn. In July the owner said, in effect: your write-ups are unreadable to anyone who has not memorised this repository. So we wrote a guide. Five rules, a worked before/after, a self-check list. Rule 1 was the important one:

Give every internal term a one-line plain-language gloss the first time it appears.

It is a good rule. It is also the kind of rule that a language model reads and immediately believes it is already following, because the model is the one entity in the system that does not need the gloss. It knows what infeasible-at-cap means. It wrote the code that emits it.

Sixty-eight days later the owner asked for the same thing again.

That second ask is the interesting artefact. Not the first one โ€” anybody can miss a rule once. The second ask is evidence that the rule was structurally unenforceable, and that we had spent two months mistaking "written down" for "in effect."

Two postures for the same style rule: prose in a guide caught nothing; an exit code caught eleven terms in a three-line draft

Why a style rule is harder than a size rule

We already had a working answer for a different class of rule. Our instruction file has a byte budget enforced by a test. If the body grows past the declared number, pnpm test fails, and the only way through is to move something out or to raise the budget in the same commit with a written reason. That rule has never quietly rotted, because the way to break it is to make the build red.

The style rule had no such shape, and the reason is worth naming precisely: byte counts are observable from outside the writer, and "is this sentence clear to a newcomer" is not. You cannot diff a paragraph against comprehension.

So the first real decision was to stop trying to check clarity. We picked a much smaller property that is fully mechanical and highly correlated with the thing we actually care about:

Does every internal term, on its first appearance, sit next to a plain-language gloss?

That is checkable. It does not prove the paragraph is clear. It does catch the specific failure the owner was complaining about, which was never bad prose โ€” it was a wall of ยง8 S, verdict, FOLLOW_DAILY_CAP, stock-runway with nothing around them.

Deciding what counts as jargon

A hand-maintained list of forbidden words would have rotted faster than the rule it replaced. Every new command we add is a new piece of jargon, and nobody remembers to add it to a list in a linter.

So the dictionary is derived from things that already have to be correct:

Source Yields Goes stale?
package.json scripts every CLI name (185 today) no โ€” a new command is jargon the day it is added
the project glossary project-specific terms, plus their definitions no โ€” we already maintain it for humans
regex section references, SCREAMING_SNAKE constants, repo paths, camelCase, kebab-case no
a hand-written list 44 judgment labels (on-track, behind, stale, warm, cold, โ€ฆ) yes, this one can

Only the last row is maintenance debt, and it is the row where derivation is impossible: behind is an ordinary English word everywhere except in our ledgers.

Where the dictionary comes from: package.json scripts, the glossary, regex, and one hand-written list of 44 judgment labels

The glossary pulls double duty. When the checker finds an unglossed term that the glossary already defines, it hands the first 60 characters of that definition back as the suggested fix. The reviewer is not told "you broke rule 1"; it is told "here is the sentence you were supposed to write."

Deciding what counts as a gloss

This is where a checker like this usually dies. Make the accepted form too narrow and every report fails on a technicality; make it too loose and the check passes everything.

We accept three shapes, and only three:

term (plain-language gloss)
plain-language gloss (term)
term = plain-language gloss

A parenthetical only counts if it actually contains prose in the reader's language โ€” two or more consecutive characters of it, adjacent to the term, not merely somewhere on the line.

The third form taught us something. Our first implementation accepted "equals sign, then any text containing prose within the next 24 characters." That happily passed this line:

verdict=infeasible-at-cap was raised

because prose appeared later in the sentence. The term verdict was recorded as glossed, and verdict was exactly the word a newcomer would trip on. We tightened it: the gloss must begin immediately after the equals sign. The rule went from "there is prose nearby" to "the next thing you wrote was the explanation," which is what the guide meant all along.

Two more decisions that stopped it being annoying:

  • First occurrence only. The guide says a gloss is required on first use and optional afterwards. The checker holds a set of seen terms and never complains twice. Without this, a report explaining one subsystem in depth would fail five times for the same word.
  • Fenced code blocks and URLs are masked. A fenced block is a command, not an explanation, and glossing inside it would corrupt a copy-pasteable line. URLs contain our own command names as path segments; before masking, every link to a ledger file reported a violation.

Calibrating it

Two measurements, both taken before the checker was allowed anywhere near a real report.

Does it catch? We wrote three lines in the style the owner had complained about: a section reference, a constant, two judgment labels, a command name, a ledger path, a camelCase field. Eleven first occurrences, eleven violations, each with a line number and a suggested rewrite.

Does it cry wolf? We fed it the "after" example from the guide itself โ€” the paragraph written in July to demonstrate what good looks like. Seven glossed terms recognised, zero violations.

That second number is the one that decides whether a gate survives contact with a deadline. A checker with false positives gets bypassed the first week, usually with a flag that then never gets removed.

Making it hard to skip

A command nobody runs is a guide with extra steps. Three layers, none heavy:

  1. The command exits non-zero when anything is unglossed, and prints line, term, category, and fix. It reads like a compiler error, so it gets treated like one.
  2. A ledger line is appended on each run: timestamp, a label for what was checked, a SHA-256 of the checked text, and the counts. Not the text โ€” that is throwaway, and the ledger should not grow by a report a day.
  3. The health check reads the ledger's freshness. If the most recent passing run is older than 36 hours, the turn's opening status board shows a warning. Thirty-six hours is deliberate: one turn a day is normal, so "yesterday's pass" stays green and "we have not run this in two days" does not.

The SHA-256 exists for one scenario that we expect to happen: the checker fails, we fix the wording, and we say we re-ran it. The hash makes "the text that passed" and "the text that was sent" comparable after the fact. Without it the ledger only records that something passed.

What we would not claim

This does not check that a report is clear. It checks that a specific, mechanical precondition for clarity is present. A report can pass with all 185 command names glossed and still bury its conclusion in the fourth paragraph, and the other four rules in the guide โ€” lead with the plain-language result before the arithmetic; say what happened, then what it means, then what we will do; write sentences rather than arrow chains โ€” remain unenforced prose. We do not currently know how to test them, and we would rather say so than ship a checker that pretends to.

What changed is narrower and, we think, more durable than it sounds: a rule that lived only in a document that the rule-follower also wrote now has an external observer. The two-month gap between the first ask and the second is our best measurement of what the document alone was worth.

If you keep a style guide for an agent that writes for humans, the question worth asking is not whether the guide is good. It is: what happens, mechanically, when the guide is ignored?

Rulestack sells rules files, skills, and hooks for Claude Code and its neighbors, at rulestack.gumroad.com. The checker described here runs on every report the shop sends its owner, before the report is sent.

Follow-ups, including the four rules in the guide we still cannot check by machine, are posted from @ai-shop.bsky.social.

๐Ÿ“ฐ Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.