Blog

Design docs matter more than ever. Now they can actually be enforced.

In the AI era, a written design decision steers every generated line, and an unenforced one gets violated at machine speed. Here's how Striff extracts structural facts from every PR and turns design-doc decisions into checks, with a real enforcement example.

For twenty years, the design doc had one consumer: other humans. You wrote down the boundaries (“the core never depends on plugins,” “the Application layer only touches Domain abstractions”) and hoped the next engineer would read it before their first PR.

That world is gone, and it changed in both directions at once.

Docs got more valuable. Coding agents read your project docs as context: CLAUDE.md, AGENTS.md, architecture notes, design docs. A decision you write down once now steers thousands of generated lines. A constraint you never wrote down simply doesn’t exist to the agent generating your code.

And docs got easier to violate. The half-life of an unenforced decision was already short when humans shipped a few PRs a day. At AI-assisted volume, a boundary that lives only in a document gets crossed at machine speed, by contributors and agents who never opened it.

A design decision without a mechanism is a wish

Here’s the uncomfortable evidence. When we ran structural analysis across major open-source repos, one of the cleanest findings came from modular-monolith-with-ddd, a repository whose entire purpose is to demonstrate clean architecture. Its layering rules aren’t buried in a wiki; they’re the point of the repo. Striff still caught the Application layer specializing a concrete Domain event, in direct violation of the documented rule.

If the flagship documented architecture drifts, yours will too. Not because anyone is careless, but because no step in the merge path ever checks the diff against the decision.

The life of an unenforced design decision

1

Decided & written

"The core must never depend on plugins." Reviewed, approved, committed to the docs.

2

Followed, for a while

The authors remember it. Early PRs respect it. Nothing checks it.

3

Quietly violated

A refactor, human or AI-written, adds one edge in the wrong direction. The diff looks clean. It merges.

4

The doc is now fiction

Reality and the doc disagree. Every future reader, and every agent using it as context, is being misled.

Step 3 is where review should intervene. But a boundary crossing isn't visible in the lines of a diff, so line-level review waves it through.

The cruelest part is step 4. In the AI era, a stale design doc isn’t just unhelpful. It’s actively harmful, because agents treat it as ground truth and generate more code on top of a fiction.

How Striff turns decisions into checks

Enforcing a design decision requires two things no diff reader has: an actual model of the system’s structure, and findings grounded in facts rather than plausible-sounding prose. Striff’s pipeline is built around exactly that:

The Striff pipeline, per pull request

1

Parse → facts

The PR is parsed into components, relationships, and OOP metrics: afferent/efferent coupling, layer depth, every new and deleted edge. Deterministic, not generated.

2

Score the structure

Structural analysis and anomaly detection over the graph: boundary crossings with no prior edge, cycles and near-cycle seeds, coupling spikes on high-dependency nodes. Your architecture docs join in here: decisions in ARCHITECTURE.md, ADRs, and design notes become constraints the graph is checked against.

3

Explain, grounded

A neurosymbolic layer writes the review note, but every claim must trace back to an extracted fact or a cited document. No hallucinated architecture commentary.

The order matters: facts are extracted before any AI writes a word, so the explanation is constrained by the graph, not the other way around.

“Facts” is not a figure of speech. Here is what stage 1 actually extracted on apache/pinot #19073, the PR where the core engine took its first-ever dependency on a plugin:

Extracted facts → finding

new_edge core.util → plugin.inputformat.avro
prior_edges this direction: 0 (first in repo history)
layer_skip layer 1 → layer 3 (1 layer skipped)
afferent AvroUtils: 24 dependents on the modified contract
efferent SegmentProcessorAvroUtils: 36 → 44
⚠ HIGH New directional boundary crossing: plugin boundary reversed

Every number is measured from the parsed graph. The finding, and the fix suggestion (invert the dependency, or extract a core-owned interface the plugin implements), is assembled from these facts, so it can't drift into plausible-sounding fiction.

What enforcement looks like on a real PR

Facts are half the story. The other half is the doc. Here’s a real one: Apache Fineract’s REFACTORING.md documents where command handlers belong. A PR placed DefaultCommandHandlerManager somewhere else. Striff read the doc, read the graph, and called out the mismatch, on the diagram, citing the file by name:

apache/fineract: the doc says one thing, the PR does another

apache/fineract · REFACTORING.md Striff structural diagram of an Apache Fineract PR, zoomed on DefaultCommandHandlerManager, which the documentation says belongs in the handler package
HIGH
Package structure violation. DefaultCommandHandlerManager sits in implementation, but REFACTORING.md requires command handlers to live in handler. The finding cites the exact document, so the review conversation starts from your team's own written decision, not a tool's opinion.

No rules engine, no configuration DSL. The documentation is the rulebook: write the decision down and every PR gets checked against it.

Your design doc, as a set of findings

Look at what the classic design-doc rules become when a PR is modeled as a graph change. These are all real findings from real public PRs:

Design decision → enforced check

The design doc saysStriff detected, on a real PR
"Command handlers live in the handler package."DefaultCommandHandlerManager placed in implementation in Apache Fineract, cited against REFACTORING.md
"The core never depends on plugins."First-ever core.util → plugin.avro edge in Apache Pinot, flagged HIGH before merge
"Application depends on Domain abstractions only."Application layer specializing a concrete Domain event in modular-monolith-with-ddd
"No package cycles."A new cycle through the CloudFormation services in floci, caught in the PR that created it

None of these rules is exotic; they're the first page of any architecture doc. What's new is that each one is now checked on every PR, instead of remembered on some.

This is the closed loop: the design doc states the decision, the graph makes it measurable, and every pull request, human-written or AI-written, gets checked against it automatically. Your doc stops being a wish and starts being a gate.

Write the doc. Then wire it to reality.

If your team is leaning into AI-assisted development, the play is not to write fewer docs. It’s the opposite. Write the decisions down, because agents will read them. Then make sure something structural is watching every PR, because agents (and humans) will also violate them: cleanly, plausibly, three lines at a time.

Install the browser extension and open your next pull request. Striff extracts the facts and checks the structure automatically. No rule-writing, no config.