Skip to content

Documentation Standard

This is the spec every BoB doc must obey. Each doc declares one Diátaxis mode, lives in the matching docs/ category, carries a frontmatter contract (type / owner / last_reviewed), and states each fact once (link, don’t duplicate). The governance tooling (R2) and CI (R3) enforce what follows; this document is the source of truth they read.

This doc is itself reference mode (look things up; don’t read end-to-end). It lives in docs/reference/ and carries the frontmatter it mandates — it is its own first conformance test. It grounds out in doc-ia-audit-2026-06-22.md, which catalogues the disorder this standard exists to end.

Principles it encodes: Krug (scannable, self-evident), Rosenfeld & Morville (findability through organization / labeling / navigation), Diátaxis (one mode per doc), inverted pyramid (answer first), and the Prime Directive (one owner per fact).


Every doc serves exactly one of four user needs. Mixing modes is the single biggest failure in BoB’s current docs — the 1,455-line handbook is all four at once, so no reader’s need is met cleanly. Pick one mode before you write; if a doc needs two, it is two docs.

Mode Serves Reader is… Answers Voice
Tutorial Learning a beginner, learning by doing “Teach me, start to finish” “We will… now do this.” Lessons, not options.
How-to A task a practitioner with a goal “How do I X?” Imperative steps. Assumes competence.
Reference Looking up someone who knows what they need “What exactly is X?” Dry, exhaustive, scannable. Tables over prose.
Explanation Understanding someone building a mental model “Why is it this way?” Discursive. Trade-offs, history, rationale.

Ask what the reader is doing when they open the doc:

  • Studying with no specific goal yet → tutorial (docs/tutorials/).
  • Working, with a concrete task in hand → how-to (docs/how-to/).
  • Pausing mid-task to confirm a name, flag, field, or value → reference (docs/reference/).
  • Stepping back to understand why a thing exists → explanation (docs/explanation/).

Two tests resolve almost every case:

  1. The “during vs. between” test. Tutorials and how-tos are read while doing; reference and explanation are read between doing. If your steps and your rationale fight for space, split them.
  2. The completeness test. Reference must be complete and accurate (every flag); a how-to must be sufficient for the goal (only the flags that matter). A doc that can’t decide how much to include is mixing the two.

Templates that already do this well: warp-drive.md’s TL;DR-first lede (how-to done right) and verification-system.md’s tight reference scoping. Reuse their shape.


2. Directory taxonomy & the root-docs rule

Section titled “2. Directory taxonomy & the root-docs rule”

Findability comes from organization + labeling (Rosenfeld & Morville). Every doc lives in exactly one category, and the category name is the Diátaxis mode — so a doc’s location declares its contract. No more flat pile at the top of docs/.

Directory Holds Mode Examples (target home)
docs/tutorials/ Learning paths, getting-started walkthroughs tutorial Quick Start, first-project setup
docs/how-to/ Task recipes — “how do I X?” how-to deploy, new-machine-setup, cdfork
docs/reference/ Lookup material — contracts, glossaries, tables, this standard reference hooks reference, CLI tables, frontmatter contract
docs/explanation/ Architecture, rationale, the conceptual spine explanation prime-directive, orchestration/ (handbook + orchestrator)
docs/audits/ Dated point-in-time analyses (named *-YYYY-MM-DD.md) reference (frozen) doc-ia-audit-2026-06-22
docs/research/ Exploratory notes, not-yet-normative findings explanation (draft) model-routing research
docs/archive/ Retired docs & tombstones (see §5) superseded guides

Rules:

  • Every docs/*.md lives in a category subdirectory. A bare *.md at the top of docs/ is a placement violation (the one exception is a generated docs/index.md navigation root, if present).
  • The category must match the doc’s type frontmatter. type: reference in docs/how-to/ is a contradiction the auditor flags.
  • Filenames are lowercase kebab-case (^[a-z0-9]+(-[a-z0-9]+)*$), extension .md for prose — already enforced by make check-doc-naming.
  • Audit/research artifacts are dated and frozen: they record a moment, are never edited after publication, and don’t carry a rolling last_reviewed expectation.

The repository root is not a documentation directory. Stray root docs (plan.md, bob-identity.md) are how BoB’s IA rotted. The root *.md allowlist is closed:

File Allowed at root? Rationale
README.md ✅ Required Repository entry point; GitHub renders it.
CLAUDE.md ✅ Required Claude Code reads it at the root by contract.
CHANGELOG.md ✅ Allowed Release tooling convention; tools expect it at root.
LICENSE / LICENSE.md ✅ Allowed Legal convention; GitHub detects it at root.
CONTRIBUTING.md ✅ Allowed GitHub surfaces it from the root in PR/issue flows.
Anything else Move into docs/<category>/ No exceptions; relocate and leave a tombstone if linked.

Why these five are exempt: each is read at the root by a tool or platform contract (GitHub, Claude Code, release automation), not by human browsing. A doc a person would seek belongs in the taxonomy. R3 enforces this allowlist in CI.


Every doc under docs/ (excluding frozen audits/ and archive/ tombstones) MUST open with a YAML frontmatter block. This is the metadata the freshness, ownership, and mode-purity checks read — no frontmatter, no governance.

---
type: reference # required — one of: tutorial | how-to | reference | explanation
owner: paulirv # required — GitHub handle accountable for accuracy
last_reviewed: 2026-06-22 # required — ISO 8601 (YYYY-MM-DD) date last verified true
expires: 2026-12-22 # optional — ISO date after which the doc is presumed stale
---
Field Required Type Contract
type Yes enum Exactly one Diátaxis mode. Must match the directory (§2.1).
owner Yes string A GitHub handle (not a team, not “team”) — one accountable human.
last_reviewed Yes ISO date YYYY-MM-DD. Bumped only when the content is re-verified, never as a side effect of an unrelated edit.
expires No ISO date If set and past, the auditor flags the doc stale regardless of last_reviewed. Use for docs with a known shelf life (migration guides, dated plans).
title No string The doc’s short label in its section index. Falls back to the H1, which is often longer than a table row wants.
description No string One sentence — the doc’s row text in its section index (§3.1). Falls back to nothing; the row still appears.

Rules:

  • type is the single source of a doc’s mode. The H1 title, the directory, and type must agree; disagreement is an auditor finding.
  • last_reviewed means “a human confirmed this is true on this date.” Sourced via timelord, never from memory. A mechanical edit (fixing a count, repairing a link) does not reset it — only re-reading for accuracy does.
  • owner is one accountable handle. “One owner per fact” (§4) starts here: every doc has exactly one person who answers for it.
  • Frozen artifacts are exempt. docs/audits/* and docs/archive/* tombstones record a moment and don’t carry a rolling review expectation; they may omit frontmatter or carry a type: reference with the publication date.

3.1 Section indexes are derived, not maintained

Section titled “3.1 Section indexes are derived, not maintained”

Section indexes (docs/how-to/index.md, docs/reference/index.md, …) are generated from the directory contents — putting a file in a section is all it takes to make it reachable (#1443). There is no second step, and no index file to remember to edit.

Two surfaces read the same derivation, so they cannot disagree:

  • The table is regenerated by scripts/docs-site/lib/section-index.js — on every docs-site build, and via make docs-sync. make docs-check verifies it and names the fix when an index is stale.
  • The orphan detector treats derived membership as an inbound link, so a doc that lives in a section is never an orphan merely because the committed table has not caught up. Without this, “add a doc” would still be two steps — the manual edit would have moved, not disappeared.

What this means when you write a doc:

  • Give it title and description. That prose is its index row — it lives next to the doc so it cannot drift from what the doc became.
  • Only the region between the BEGIN/END GENERATED SECTION INDEX markers is machine-written. The index’s frontmatter, H1, and intro blockquote are hand-authored and stay that way — curating what a section is for still has value; curating a list of files does not.
  • Omitting description degrades the row, never the membership: the doc still appears, falling back to its H1.
  • Rows sort by filename, so ordering is derived too — there is no hidden hand-ordering to drift.

3.2 An orphan is caught at commit time, not in CI

Section titled “3.2 An orphan is caught at commit time, not in CI”

Derived membership covers a doc that lands in a section directory. A doc that lands anywhere else — a nested subdirectory with its own index, a new directory, the docs/ root — still needs an inbound link, and until #1444 that gap was discovered by make docs-check in CI, on master, after the change had landed.

hooks/doc-orphan-gate.sh (PreToolUse) now refuses the commit instead, naming both the unreachable file and the index that should link it. It runs on any branch and needs no PR. Detection is not a second implementation: the hook shells out to scripts/doc-keeper/orphan-check.js, which runs the auditor’s own link-graph rule over rules.json, so the local gate and the CI gate cannot disagree about what an orphan is.

Terminal window
node scripts/doc-keeper/orphan-check.js --staged # what the gate will say
node scripts/doc-keeper/orphan-check.js --all # whole-tree, ad hoc

The gate fails open — any internal error allows the commit, because make docs-check remains the non-bypassable backstop. Escape hatch for a project that does not publish its docs: _workflow.suppress_doc_orphan_gate=true.


Mode and metadata get a doc filed; these principles get it read. They apply to every mode, though the emphasis shifts (reference leans hardest on scannability; explanation on the inverted pyramid’s “why”).

Scope: this section owns documentation prose. For prose rules that span all authored artifacts — commit messages, PR/issue bodies, comments — see the Writing Styleguide, which references these principles rather than restating them.

Lead with the conclusion, then the detail, then the background. A reader who stops after the first paragraph should still have the answer. This is why every BoB doc opens with a TL;DR blockquote (see this doc’s lede, and warp-drive.md’s).

  • First screen carries the payload. Put the “what is this / what do I do” above the fold; push history, edge cases, and rationale down.
  • Never bury the lede in setup. A how-to that spends two screens on context before the first command has the pyramid upside-down.

4.2 Scannability — readers skim, they don’t read

Section titled “4.2 Scannability — readers skim, they don’t read”

Krug’s law: people scan for what they need and ignore the rest. Write for the scan.

  • Descriptive headings that say what the section answers — a reader navigating by heading alone should find their answer. Avoid cute or generic headings (“Overview”).
  • Tables over prose for any enumerable set (flags, fields, modes, options). If you’re writing “X does A, Y does B, Z does C,” it’s a table.
  • Short paragraphs (≤ 3–4 sentences) and bulleted lists for steps or sets.
  • Front-load the keyword. Start list items and table rows with the term being defined, not with filler (“You can use the --foo flag to…” → “--foo — …”).
  • Bold the load-bearing phrase in a dense paragraph so the skim catches it.

4.3 Controlled vocabulary — one name per thing

Section titled “4.3 Controlled vocabulary — one name per thing”

Findability dies when the same concept has three names (Rosenfeld & Morville). BoB uses one canonical term per concept, everywhere:

  • One name per concept. BOB_SOURCE (not “the source repo” / “the git repo” interchangeably); “requirement” (not “ticket” / “story” / “task”); “warp-drive” (not “the loop” / “auto-dev”). Pick the canonical term; use it verbatim.
  • One casing/spelling per name. dev.json, cdprov, make check — as written, every time.
  • Define a term once, at its canonical home, and link to that definition rather than re-defining it inline (this is §4.4 applied to vocabulary).

4.4 Single source of truth — one owner per fact

Section titled “4.4 Single source of truth — one owner per fact”

The Prime Directive, applied to prose: every fact is maintained in exactly one doc; everywhere else links to it. Duplication guarantees drift — the audit found the architecture diagram copied in four places and no two component counts in the repo agreeing.

  • State each fact once, at its owning doc, then link. A count, a table, a diagram, a procedure lives in one place.
  • Link, don’t paste. If you’re tempted to copy a table “for convenience,” link to it instead. Convenience copies are drift waiting to happen.
  • The owner is the doc whose type/topic the fact most belongs to. A make check target table belongs in the verification reference; everything else links there.
  • If two docs legitimately need the same fact, one owns it and the other transcludes or links — never two hand-maintained copies.

The R2 auditor’s cross_cutting[] / duplication detector exists to enforce this mechanically. Until it ships, treat duplication as a review-blocking defect.

4.5 Issue/PR bodies — never use a bare #N as an item number

Section titled “4.5 Issue/PR bodies — never use a bare #N as an item number”

This applies to any text a command authors into a GitHub issue, PR, or comment body (not docs/ prose). GitHub autolinks every #<digits> in rendered markdown into a cross-reference to the issue/PR holding that number. In a mature repo those low numbers are unrelated closed issues, so a body that numbers its own items — “Critical #1”, “High #6”, “fix Medium #10” — sprouts a cluster of spurious, misleading links (the real cause of nanawallweb/nanawalld8#674; tracked here as #520).

  • Reserve #N for genuine issue/PR references only (Part of #42, fixes #51, the (#260) provenance tag).
  • For an intra-document item / finding / step number, use a non-linking form: C1 / H2 / M3 (severity+ordinal), “item 1”, “finding 3”, or backtick it — `#1`.
  • #N inside a fenced code block or backticks does not autolink and is safe (e.g. a terminal mockup listing real issue numbers).

Enforced by scripts/checks/check-issue-autolink.sh (wired into make check), which flags item-word + bare #N in the issue-authoring command templates and can lint a generated body file before gh issue create.

4.6 How-to docs — show both the slash command and the terminal command

Section titled “4.6 How-to docs — show both the slash command and the terminal command”

A BoB task can usually be run two ways: as a slash command inside a Claude Code session (e.g. /warp-drive), or from a terminal as a script / bin/ entrypoint / make target (e.g. warp, node ~/.claude/scripts/...). A how-to must make both paths explicit and labelled, so a reader always knows which world a command belongs to — never leave them guessing whether /doc-bootstrap is something they type in a shell.

  • Label every runnable command with the path it belongs to. Use the headings Slash command (in a Claude Code session) and Terminal (a shell, outside any session), verbatim, every time.
  • Show both when both exist. Where a task has a slash command and a terminal entrypoint, present a labelled pair (two fenced blocks, or a two-column table). The Doc-Bootstrap how-to is the reference shape.
  • Single-path tasks say which and why. If a task can only be done one way, show that one and add a one-line reason — e.g. “Terminal only — runs before any Claude session exists” (New Machine Setup) or “Slash command only — needs the session’s conversation context”. Do not invent the missing form.
  • Never document a command that does not exist. A slash command must have a commands/<name>.md (or a slash-invocable registry skill); a terminal command must be a real bin/ entrypoint, make target, or ~/.claude/scripts/... path. Verify before writing — a fabricated invocation is a correctness defect, not a style nit.

4.7 Vue-safe prose — no raw angle-bracket placeholders

Section titled “4.7 Vue-safe prose — no raw angle-bracket placeholders”

The docs site (VitePress) compiles every page into a Vue template, so a raw <placeholder> token in prose is parsed as an HTML element and fails the site build (“Element is missing end tag”) — this class froze the published site for days in 2026-07 (#988). Two constructs produce one:

  • A bare token in prose: writing “pass –budget <usd>” without backticks. Always backtick it: --budget <usd>.
  • A backticked span wrapped across a line break whose continuation line starts with the <token>. The site build claims that line as raw HTML before backtick pairing runs, so the span never forms and the token lands in prose. Keep any code span containing <…> on a single line — rewrap the sentence, not the span.

Fenced code blocks, single-line backticked spans, HTML comments, autolinks (<https://…>), and deliberate standard HTML (<details>, <br>) are all safe. Only non-HTML tag-shaped tokens in effective prose break the build.

Enforced by scripts/docs-site/vue-safe-lint.js, which predicts the build verdict without running the build: it runs repo-wide in make docs-check (and therefore in the CI docs gate) and per-commit in the doc-drift hook, which blocks a commit that adds a vue-unsafe token to a docs-site source. Escape hatch for projects with no docs site: _workflow.suppress_vue_safe_lint=true.


Docs die. An orderly death — tombstone, archive, redirect — keeps the namespace clean and link graph intact; the current docs/ pile of six stray tombstones and orphaned artifacts is what disorderly death looks like. Never silently delete a doc that anything links to.

Situation Action
Content moved into another doc Tombstone in place (§5.2), then move the file to archive/ once links are repointed.
Doc is obsolete, nothing replaces it, but it has inbound links or historical value Archive it (§5.3) with a tombstone redirect.
Doc is obsolete and orphaned (nothing links in, no historical value) Delete it — but only after confirming zero inbound links (link-graph check).
Doc is a dated artifact (audit, status report) Leave in audits/; it’s frozen, not deprecated. Never edit; never archive.

When content moves, replace the old file with a tombstone so existing links don’t 404 and readers are forwarded. BoB’s established tombstone shape (e.g. docs/pm-cheatsheet.md):

# <Old Title> — Moved
This document has been replaced by **[<New Title>](/reference/<relative-path-to-the-new-doc>)**.
<One line on why / what absorbed it.>
  • Keep the filename and path of the original — that’s what inbound links target.
  • A tombstone is a redirect, not content. It carries no frontmatter contract and is exempt from freshness checks.
  • Tombstones live where the original lived until the next restructuring sweep, then move to archive/ (so the live namespace isn’t cluttered — the audit’s complaint about six tombstones in docs/).

docs/archive/ is the graveyard: retired docs and old tombstones that are kept for history but are out of the live IA.

  • Archived docs are frozen. Not edited, not reviewed, not counted in freshness or mode-purity checks.
  • Preserve the original basename so old links resolving through a tombstone still land.
  • Add a one-line banner at the top noting it’s archived and what superseded it, if not already a tombstone.
  • The auditor treats archive/ and audits/ as excluded from active governance but included in the link graph (so a live doc linking into archive/ for anything other than history is flagged).
  • Internal links: the tombstone is the redirect — its single link forwards the reader. Repoint inbound links to the new home opportunistically; the tombstone covers the gap until you do.
  • External / deep links (issues, external sites) can’t be repointed, so the tombstone at the original path must persist — this is the reason tombstones are kept rather than deleted outright.
  • Generated nav/index (a future docs/index.md) is regenerated from the taxonomy, so it never points at a dead doc; archived docs simply drop out of it.

This standard is enforced by the doc-keeper auditor (scripts/doc-keeper/audit.js, run via make docs-check) and in CI (.github/workflows/ci.yml). Each finding carries a severity; whether it blocks (fails CI) or warns (reported, non- blocking) is set here — a single source of truth the tooling reads.

Finding Category Blocks CI?
Broken relative link broken-link Blocks (severity high)
Short-form priority label (p1 vs p1-critical) label-name Blocks (severity high)
Orphan doc (nothing links to it) orphan Blocks — via ratchet (§6.2)
Root *.md outside the allowlist root-placement Blocks — via ratchet (§6.2)
Missing / stale required frontmatter freshness Blocks — via ratchet (§6.2)
Filename not lowercase-kebab filename-violation Blocks (make check-doc-naming, now in CI)
Vue-unsafe angle-bracket token in prose (§4.7) vue-safe-lint Blocks (make docs-check + commit-time doc-drift hook)
Single-source-of-truth duplication duplication Warns
Diátaxis mode-mixing mode-mixing Warns
Component / registry count drift component-count etc. Warns (mechanically fixable via make docs-sync)

Blocking means make docs-check exits non-zero, failing the build. Warning findings still appear in the audit report and feed the curation worklist, but never fail CI — they require human judgement (which copy is canonical, how to split a doc) that a gate can’t make.

The three governance categories (orphan, root-placement, freshness) block new violations but grandfather the existing ones, via a version-controlled baseline at scripts/doc-keeper/audit-baseline.json. This is what lets the gate go live on a repo that predates the standard without a flag-day cleanup:

  • make docs-check runs the auditor with --fail-on-categories orphan,root-placement,freshness --baseline scripts/doc-keeper/audit-baseline.json. A finding fails CI only if its signature is not already in the baseline — so a PR that adds a new orphan or a stray root doc is blocked, while the pre-existing debt is tolerated.
  • The baseline is accepted documentation debt, and the only correct direction is down. Remediation work (restructuring, archival, adding frontmatter) shrinks it; regenerate with audit.js --write-baseline scripts/doc-keeper/audit-baseline.json --fail-on-categories orphan,root-placement,freshness after a cleanup. When it reaches [], the gate is fully hot with zero suppression.
  • Never grow the baseline to silence a new finding — that defeats the ratchet. New violations are fixed, not baselined.