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 optionalresearch/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.mdwithM-NNitems 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: draftis 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_urlis required on a service document, but in a single-repository layoutservices/describes modules of one repository; andapi/assumes an HTTP endpoint reference, so a wire-protocol document has neitherservicesnorcontract_sourceto give. The rest is drift: research written beforetypebecame mandatory, and two statuses outside the vocabulary. code_anchors.pyharvests every backticked path in a document, not only the anchors table. A page mentioning/usr/localin prose gets it counted and reported as missing.- That check also needs the repositories checked out side by side. Without
--reposit prints "not checked", which is not the same as "nothing wrong". spec_versionis 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.