docs-bootstrap: a documentation format with a gate

Documentation meant for a coding agent to read is usually described in prose: keep the definitions tight, point at the code instead of retelling it, say which parts are guesses. The rules read like a format and behave like advice, because nothing looks at a document afterwards and says whether it obeyed them.

docs-bootstrap is a documentation format for codebases, plus the checks that keep it honest: layers linked by ids, every document carrying paths into the code, and six scripts CI runs on every push. MIT, Python 3.11 with PyYAML, and it also ships as a Claude Code skill.

Why it exists

The failure mode is not missing documentation. It is documentation that stops matching the code while nothing says so. A path rots because somebody renamed a directory in another repository. A page describing intent gets merged and starts reading as fact. The index in docs/README.md loses a file, and the only way to find out is to notice.

"Keep the definitions tight" has no failing case, so it is advice with a format's typography. The question this started from is which of those rules can be made to fail, and what is honestly left over when they cannot.

What it does

  • Four layers — features/, screens/, api/, services/ — with an optional research/ above them. A missing directory is a valid answer; a renamed one is not.
  • Cross-layer links are ids in the frontmatter and markdown links in the body, so the graph resolves without opening every file.
  • Every document must carry at least one path into the code — the one structural rule the checker enforces, and the reason the format exists.
  • BDD scenarios are the acceptance criteria; one covered by a test says so on an **Automated:** line, and the absence of that line is the visible part.
  • Backlog in two shapes: BACKLOG.md with M-NN items under milestones, or a file per item plus a generated index for larger trees.
  • Three blocking checks, two reports, one for the packaging, and two that only make sense on a branch: status: draft is an error on the default branch, and an item number taken by a branch that merged first is an error on a pull request.

Installation

As a Claude Code plugin, from the repository's own marketplace:

claude plugin marketplace add youndie/docs-bootstrap
claude plugin install docs-bootstrap@docs-bootstrap

Or as a clone. The directory name has to match the name: in the skill's frontmatter, so the clone path is the installation:

git clone https://github.com/youndie/docs-bootstrap ~/.claude/skills/docs-bootstrap

The checks need neither: copy scripts/ into a project and make check works against a hand-written tree.

What a document looks like

---
id: feature-borrow-and-return
type: feature
status: active
involved_services: [catalog-api, loans-service]
client_entries: [screen-checkout-desk]
---

## Code anchors

| Service | Code |
|---|---|
| loans-service | `src/loans_service/domain/loan.py` — the state machine |

id equals the filename, client_entries: [] is an answer while a missing field is a question, and anchors may be written from the repository root, from the module root, or abbreviated with ...: matching is by suffix. The gate reports what it found rather than a score:

Documents: 11
No errors
The coverage map matches the files
Anchors: 91 - found 90, not found 0, skipped 1

What is checked, and what is only reported

Errors are things one cannot be wrong about: a broken link, a missing required field, an id that is not the filename. Warnings are rules with legitimate exceptions, such as an orphan document. Reports carry no judgement.

Two rules deliberately did not become gates. The share of automated scenarios is a number rather than a threshold, because a gate on that figure teaches people to write the line without the test. Whether anchors still resolve is a report because an anchor breaks from a refactor in somebody else's repository, and a path quoted as obsolete is indistinguishable from a live one by machine. A gate that cannot be passed honestly gets switched off within a month, taking the useful checks with it.

The coverage map is checked, not generated

docs/README.md ends with every document, grouped by layer, one line of description each. Generating that list would destroy the only thing it is read for: neither the grouping nor the descriptions can be derived from the files. The machine owns the membership and the counters, the human owns the meaning, and --fix appends missing entries marked as placeholders instead of inventing sentences. The backlog index is the opposite case and is generated: a task's status lives in the task's own file, and storing it twice is how an index starts lying.

In use

Two public projects are documented this way, both single-repository and without a client, so both skip screens/ and keep the backlog as a root BACKLOG.md:

  • booblik, a JVM message broker: 11 documents — architecture and use-case research, two features, a wire-protocol reference, five module documents.
  • mongkn, a Kotlin/Native binding over mongo-c-driver: 8 documents, plus coverage and performance pages that sit outside the layers.

Limitations

  • Neither of those two passes the gate today — 11 and 12 errors. Most of it is the spec's fault: repo_url is required on a service document, but in a single-repository layout services/ describes modules of one repository; and api/ assumes an HTTP endpoint reference, so a wire-protocol document has neither services nor contract_source to give. The rest is drift: research written before type became mandatory, and two statuses outside the vocabulary.
  • code_anchors.py harvests every backticked path in a document, not only the anchors table. A page mentioning /usr/local in prose gets it counted and reported as missing.
  • That check also needs the repositories checked out side by side. Without --repos it prints "not checked", which is not the same as "nothing wrong".
  • spec_version is 1, and the worked example is synthetic: a three-service lending system written for the repository, with real code underneath so every anchor resolves. It says nothing about how the format behaves on a large tree.

Source: github.com/youndie/docs-bootstrap