Striff Engineering Product
Your architecture docs matter more than ever. Now every pull request is checked against them.
Coding agents read your docs as context, and nothing checks that the code still agrees with them. How Striff turns the sentences already in your repository into rules computed at both revisions of every pull request, and the one real violation it found in 1,394 checks.
On this page
For twenty years a design doc had one reader: other engineers. You wrote down how the system fits together (what lives where, what calls what, what must never depend on what) and hoped the next person read it before their first pull request.
That changed in both directions at once.
Docs got more valuable. Coding agents read AGENTS.md, CLAUDE.md, READMEs and architecture notes as context. A decision written down once now steers thousands of generated lines, and a constraint nobody wrote down does not exist for the agent writing your code.
And docs got easier to break. A sentence that describes the code stops being true the moment the code changes and the sentence does not. At agent volume that happens constantly, and nothing in the merge path compares the two: the diff shows the code, and the sentence is in a different file.
A real one: ecChronos #1786
Ericsson’s ecChronos schedules repairs for Apache Cassandra, and its core.impl module has the kind of README most teams only intend to write. It goes through the module class by class and says what each one does. Of NodeWorker, the background thread that runs for each Cassandra node, line 136 says:
Calls
RepairScheduler.putConfigurations()to keep jobs up to date
Pull request #1786, “Add Repair History pre and post Job in Incremental path”, changed 27 files (+1,235 −417). Part of it was a tidy refactor: a new class, SchemaRefresher, took over turning schema events into repair configurations. NodeWorker lost 150 lines, now holds a SchemaRefresher, and hands every event to it. It no longer references RepairScheduler at all. The call the README describes is still made, from line 158 of SchemaRefresher.java.
The pull request was approved and merged on 8 September. The README was not in the diff, so nobody reading the diff had a reason to open it. At the time of writing, line 136 on master still credits NodeWorker, and so does the execution-flow sketch at line 156 of the same file.
Striff checked 28 rules from that repository’s documentation at both revisions of the pull request. Three of them:
Ericsson/ecchronos #1786 · documented rules
RepairScheduler.putConfigurations() to keep jobs up to date core.impl/README.md:136 NodeWorker depends on RepairScheduler Broken by this PR The rule held before this pull request and is broken after it. ConnectionType enum (from utils) defines the three ecChronos control modes: connection/README.md:57 ConnectionType is in com.ericsson.bss.cassandra.ecchronos.utils.enums.connection General term resolved Held The rule held before this pull request and still holds after it. VnodeRepairTask / IncrementalRepairTask — Concrete RepairTask subclasses for vnode and incremental repair respectively. core.impl/README.md:65 VnodeRepairTask depends on RepairTask, and IncrementalRepairTask depends on RepairTask One sentence, two rules Held The rule held before this pull request and still holds after it. On the left, the sentence as the maintainers wrote it, with the file and line it came from. In the middle, the rule it became, in plain English and as the formula that is actually evaluated. On the right, the verdict on this pull request, stated as what it is: whether the model of the code Striff checked, before the change and after it, satisfies the formula. The second row resolves a general term, “from utils”, to the package it means; the third turns one sentence into one rule per class it names.
The violation, as facts you can check
A finding is only worth something if you can check it without trusting us. This one is a sentence and four lookups in two public revisions:
From the sentence to the verdict
The rule came out of the prose. Everything under it was computed from the parsed code at each revision, which is why the finding can name the line that moved and cannot invent one that did not.
In logical terms, the rule is a formula about the code, and the code parsed at each revision is a model that either satisfies it or does not. The whole violation is two facts:
The first line is what the model read out of the README. The second is what the program computed from the code, and it is the only thing a verdict is allowed to rest on.
A one-line fix, while it is still one line
The fix is one word in a README: NodeWorker becomes SchemaRefresher. In the pull request that caused it, that is a one-line follow-up commit, made while the author still remembers why the call moved.
Left alone, it compounds. The next contributor looking for where repair configurations are scheduled is sent to NodeWorker, and so is every coding agent handed the README as context. An agent that trusts the sentence has an obvious way to make it true again: give NodeWorker a scheduler, and re-create the dependency the refactor had just removed. A stale doc is not merely out of date. It is instructions for undoing your last refactor.
The same change, on the diagram
The documented rules are the headline; the diagram is the context for them. Every pull request also gets a structural diff of the classes the change touched, what they gained and lost, and how they connect.
Ericsson/ecchronos #1786 · framed on NodeWorker
The real render for #1786, cropped to NodeWorker. The members it lost are marked, the RepairScheduler field among them, next to the SchemaRefresher it gained. The diagram shows what moved; the rule on the README says which of those moves your docs now disagree with.
How a sentence becomes a check
Nobody at Ericsson wrote a rule for Striff. There is no rule file and no configuration. The rules come out of the prose already in the repository, in five steps, and only two of them use a language model:
Neurosymbolic, per pull request
Parse
Both revisions become a compiler-grade model of the code.
Read
A model reads your docs and proposes candidate rules.
Ground and compile
Candidates naming anything not in the code are dropped. The rest become queries.
Evaluate
Each query runs at both revisions. Only a break the change introduced counts against it.
Narrate
A model phrases what was computed. It cannot add a finding.
The model proposes and the program decides. A proposed rule that names a class the parsed code does not contain never reaches evaluation, so the model cannot invent a violation by inventing a name. How every claim traces back to your docs or your code.
Every rule Striff reports gets one of four outcomes: held (this change keeps it: nothing in this pull request breaks it), violated (broken by this change), already broken (broken in the code Striff checked, not by this change, and never blamed on it), or restored (broken before, true after). Striff only reports what it can verify from the code. A rule it cannot answer from a source parse is left out, never shown as passing.
How often this fires
Rarely, and that is the point. Across a scan of 74 public pull requests in 74 different repositories, run between 5 and 9 September, Striff checked 1,394 documented rules:
1,394 documented-rule checks, 74 pull requests
The repositories were chosen because their docs said something checkable, so this is the rate on documented codebases, not on every codebase. The one violation is the ecChronos rule above. One in 1,394 is what a check you can afford to read looks like: when it fires, it is an event.
The 49 already-broken rows are the other quiet result: rules a repository’s own documentation states that were already broken in the code Striff checked, not by the pull request. They are never blamed on the pull request in front of you, and they are never shown as a pass either. Whether the fix belongs in the code or in the sentence is the maintainer’s call. The difference is that somebody now knows.
Writing docs that can be checked
You do not have to change how you write to get value from this; ecChronos’s README was written for people, not for us. But sentences of a certain shape get checked, and it is worth knowing which:
- Name real things.
NodeWorkeris checkable. “The worker” is checkable only if one class answers to it. Backticks help a reader and cost nothing. - Say where things live and what they use. “
ConnectionTypeis inutils.” “NodeWorkercallsRepairScheduler.” Placement and dependency are the relations a parser can answer at both revisions. - Say what must not happen. “The core never imports from plugins” is a rule the moment it is written down, and it is the kind of rule an agent in a hurry is most likely to break.
- Leave intent as prose. “We prefer composition” is not a claim about structure, and nothing will pretend it is one.
- Keep the agent files honest too.
AGENTS.mdand.github/copilot-instructions.mdare read by the same process, so the layering and naming rules you give your agents are checked against the code they write.
Write the doc. Then wire it to reality.
If your team is leaning into AI-assisted development, the move is not to write fewer docs. It is the opposite. Write the decisions down, because agents will read them. Then make sure something checks every pull request against them, because agents and humans will also break them: cleanly, plausibly, a few lines at a time, in files nobody thought to open.
Install the GitHub App and open your next pull request. Striff reads the docs you already have and checks every rule in them, with nothing to write and nothing to configure. For a public pull request in somebody else’s repository, the browser extension does the same in a tab beside Files changed.