Skip to content
Guide contents

Concept and Design Principles

← Guide index

1. The starting point: an AI does not doubt a design doc

Design docs start rotting the moment you write them. That is an old problem, and until now it held together because a human doubts the doc while reading it.

A human opens a design doc, reads a few lines, and notices something off. “That function name is gone.” “This diagram predates the refactor.” Then they stop and check the code. The act of doubting is silently wedged into every read.

An AI does not doubt. If it says status: current, the AI treats it as current. And because it is obedient, it drags the implementation back toward the doc.

In practice this shows up as:

  • Writing a new feature against an API name that was removed six months ago
  • Deciding “this doesn’t match the spec” and reverting working code to an older design
  • Reintroducing an abstraction that was already replaced, because the doc still lists it

The awkward part is that all of these are the AI faithfully following instructions. No amount of prompt tuning fixes it. The information you handed it is stale, so the information is what has to change.

Why the usual approaches fall short

Common approachWhy it isn’t enough
Write a “last updated” date in the docSelf-reported. People forget, and it also ticks over for cosmetic edits. It has no link to the code
Catch it in reviewHuman review cannot keep up with how often and how much an AI reads design docs
Drop the docs, keep only codeThe AI loses all context. Feeding it the whole codebase every time is expensive, and “why is it like this” cannot be recovered anyway
Tell the AI in the prompt that docs may be staleNow every doc is equally suspect. Which one is actually stale remains unknown, so none of them get used

What is needed is for a machine to determine which docs are stale, and tell the AI about those specifically.

2. The approach: make freshness machine-checkable

docs-kit puts two pieces of information in each document’s front matter.

FieldMeaning
scopeThe code this document is responsible for (globs)
verified_atThe commit SHA at which it was last reconciled against the implementation

With those two, the judgement can be handed to git.

There are three verdicts.

VerdictMeaningexit code
okNo changes worth reviewing inside scope since verified_at0
staleThe implementation moved. The doc needs review1
brokenThe premise of the check is broken (SHA not in history, scope matches nothing, …)2

That verdict fails CI, and the same verdict reaches the AI over MCP. The AI then treats stale docs as reference material and trusts the code instead.

Why a commit SHA

Several things could serve as the freshness baseline, but only a SHA actually ties back to the code.

BaselineProblem
A “last updated” date in the docSelf-reported. Forgotten. Bumped by typo fixes. Unrelated to code changes
File mtimeDestroyed by checkout. In CI every file has the same timestamp
The doc’s last git commit dateThat is “when the doc was touched,” not “whether it was reconciled with the code”
A human declaring it staleOnly someone who noticed can declare it — and not noticing is the whole problem
The commit SHA at reconciliation timegit history lets a machine reconstruct exactly what happened inside scope since then

With a SHA, the judgement rests on a fact in the repository rather than on somebody’s memory. The cost of declaring it is one command: docs-kit verify <id>.

It only judges “should this be reviewed”

This is easy to misread, so stated plainly: docs-kit does not judge whether a document’s contents are correct. It judges only whether the implementation moved such that a human or AI should look at the doc again.

So stale means “unverified,” not “wrong.” If you review it and conclude it was still accurate, just run docs-kit verify and it goes back to ok.

3. Design principles

Storage principles

Markdown is the source of truth. Just Markdown files with front matter — no custom markup extensions of any kind. If docs-kit disappears tomorrow, docs/ still reads fine in an editor or on GitHub. Not locking you in is the precondition for writing docs without hesitation.

Exactly four layers. Documents live in adr / spec / arch / plan, and that set cannot be extended. If layers were configurable, every repository would place things differently, and an AI would have to relearn where to look each time.

LayerRoleLifetime
adr/Decision records — why it was done this wayPermanent (never rewritten)
spec/Current specification — how it works nowKept in sync with the code
arch/Architecture — structure and dependency directionKept in sync with the code
plan/Temporary work plansDisposable (deleted at merge)

Decisions are immutable; append only. An ADR is never rewritten. When the direction changes, add a new ADR and mark the old one status: superseded with superseded_by. Both “why we decided that” and “why we changed” survive as history. Rewriting destroys the reasoning trail and you end up relitigating the same argument.

No progress state. done / doing / TODO checkboxes do not belong in design docs. That is the issue tracker’s job. Mixed in, “reading the design” and “checking progress” become the same action, and both rot.

Judgement principles

Four fixed status values. current / draft / stale / superseded, and nothing else. Allowing per-project values like reviewing or wip would leave an AI unable to decide whether a given status is trustworthy. Anything that changes how the AI reads is not made configurable.

Err toward false positives. Judgement is per file; there is no line-level correlation. If a file inside scope changes, the doc needs review regardless of what changed. Missing a case (a stale doc sitting at ok) costs far more than an extra flag (a stale you check and find fine).

Absorb only what can safely be absorbed. “Everything is stale” would not be workable, so diffs that clearly cannot affect the spec are excluded: test-code changes, changes made and undone within the range (net-zero), pure renames with no content change. Details in Workflow.

AI integration principles

Do not add configuration. One config file, docs.config.yaml, with four keys. Layer structure, schema, status values, INDEX format, and the CLAUDE.md block contents are all non-configurable. The reason is consistent: so that the AI read/write protocol is identical in every repository. The moment it becomes configurable, AI behaviour diverges per repo, you document the divergence in CLAUDE.md, and that CLAUDE.md rots too.

MCP does not carry document bodies. The MCP server returns absolute paths, metadata, and a 200-character summary — it never returns or writes the body. Reading and editing the body is left to the AI’s own Read / Edit tools. Routing bodies through MCP would cost you diff display, permission prompts, and partial edits all at once.

Measurement bears this out. One docs_which call returned 15 documents; only 4 were actually opened. The rest were ruled out from title and summary alone.

4. Deliberately not built

Documentation tools expand without limit. The essential problems are freshness and findability (being able to read selectively); everything else leaves only maintenance cost behind. The list below is “decided against,” not “not yet.”

Not builtReason
A documentation site rendererAn editor and GitHub are enough for reading. If Markdown is the source of truth, a dedicated viewer is redundant
Vector / semantic searchGlob-based reverse lookup is the main bet. Semantic search stays on the shelf as insurance if reverse lookup misses
Custom markup extensionsBreaks Markdown compatibility, which collides head-on with the no-lock-in principle
Progress and task managementThat is the issue tracker’s territory
Resident daemon / HTTP / dashboardFrozen with the design left intact. The CLI and stdio MCP already deliver the value; what is missing is convenience, not capability

When considering a new feature, the first question is “does it help freshness or findability?” If not, it does not get built.

5. Where it works and where it doesn’t

Where it works

  • Documents whose code range can be delimited with scope (spec / arch). The tighter the correspondence, the better the precision
  • Repositories worked on by several people plus AI in parallel. CI automatically catches when one person’s change staled someone else’s doc
  • Any situation where you want the AI to pick which docs to read. Reverse lookup narrows the candidates

Where it doesn’t (accepted trade-offs)

  • Documents without scope carry no freshness guarantee. Most ADRs fall here. ADRs record past decisions and do not need to track the code, so this is fine in itself — but be aware that guaranteed and unguaranteed docs coexist
  • No line-level correspondence. A one-line change inside scope marks the doc stale. Too broad a scope means permanent staleness and the check becomes noise, so keep ranges tight
  • Unrelated changes bundled into a verify commit are missed. If the implementation change, the doc update, and the verify all land in one commit, that implementation change is not detected. Operationally: keep verify commits to the corresponding change only
  • Changes that arrive only via a merge commit are not tracked. Anything introduced solely during conflict resolution drops out. Squash merges are recommended
  • Correctness is not judged. Only “should this be reviewed” (see §2)

Next