Concept and Design Principles
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 approach | Why it isn’t enough |
|---|---|
| Write a “last updated” date in the doc | Self-reported. People forget, and it also ticks over for cosmetic edits. It has no link to the code |
| Catch it in review | Human review cannot keep up with how often and how much an AI reads design docs |
| Drop the docs, keep only code | The 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 stale | Now 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.
| Field | Meaning |
|---|---|
scope | The code this document is responsible for (globs) |
verified_at | The 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.
| Verdict | Meaning | exit code |
|---|---|---|
ok | No changes worth reviewing inside scope since verified_at | 0 |
stale | The implementation moved. The doc needs review | 1 |
broken | The 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.
| Baseline | Problem |
|---|---|
| A “last updated” date in the doc | Self-reported. Forgotten. Bumped by typo fixes. Unrelated to code changes |
| File mtime | Destroyed by checkout. In CI every file has the same timestamp |
| The doc’s last git commit date | That is “when the doc was touched,” not “whether it was reconciled with the code” |
| A human declaring it stale | Only someone who noticed can declare it — and not noticing is the whole problem |
| The commit SHA at reconciliation time | git 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.
| Layer | Role | Lifetime |
|---|---|---|
adr/ | Decision records — why it was done this way | Permanent (never rewritten) |
spec/ | Current specification — how it works now | Kept in sync with the code |
arch/ | Architecture — structure and dependency direction | Kept in sync with the code |
plan/ | Temporary work plans | Disposable (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 built | Reason |
|---|---|
| A documentation site renderer | An editor and GitHub are enough for reading. If Markdown is the source of truth, a dedicated viewer is redundant |
| Vector / semantic search | Glob-based reverse lookup is the main bet. Semantic search stays on the shelf as insurance if reverse lookup misses |
| Custom markup extensions | Breaks Markdown compatibility, which collides head-on with the no-lock-in principle |
| Progress and task management | That is the issue tracker’s territory |
| Resident daemon / HTTP / dashboard | Frozen 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
scopecarry 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
- The actual operating procedure → Documentation workflow
- Just get it running → Installation
- You already have design docs → Adopting in an existing project