# docs-bootstrap: a documentation format with a gate

> Documentation formats for coding agents are written as prose rules that nothing verifies. docs-bootstrap is the same idea with checks that can fail, run by CI against the repository's own example.

Page: https://kotlin.website/blog/docs-bootstrap · Published: 2026-08-19

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](https://github.com/youndie/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:

```bash
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:

```bash
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

```markdown
---
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:

```text
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](https://github.com/youndie/booblik), a JVM message broker: 11 documents — architecture
  and use-case research, two features, a wire-protocol reference, five module documents.
- [mongkn](https://github.com/youndie/mongkn), a Kotlin/Native binding over `mongo-c-driver`: 8
  documents, plus coverage and performance pages that sit outside the layers.

## Limitations

- **Neither of the two trees above passes the gate today** — booblik reports 6 errors, mongkn 9.
  That is drift rather than a limit of the format: both were written under an earlier version of
  the skill and never brought forward, so their research documents predate `type` becoming
  mandatory, two of their statuses are outside the vocabulary, and one document has no frontmatter
  at all. A tree written to the current spec passes; that was checked by documenting a small
  project from scratch in the same single-repository layout.
- `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".
- Nothing here verifies that a sentence is **true**. The checks confirm that the things a document
  names exist and that the graph resolves; whether the paragraph next to the path describes the
  code is a question for a reader.
- `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](https://github.com/youndie/docs-bootstrap)
