Documentation publication contract
CrewRig's technical documentation is a structured body of reference material that ships inside the framework. This page is the normative contract for how each documentation page declares its place in that structure, how a machine-readable index of the public subset is derived, and how a separate website repository consumes that index without coupling to CrewRig's internal directory layout. It realizes spec 0027.
Top-level sections
Every page that belongs to a top-level documentation section belongs to exactly one of the following eight sections, in this fixed order:
| # | section value |
Title |
|---|---|---|
| 1 | introduction |
Introduction |
| 2 | concepts |
Concepts |
| 3 | adoption |
Adoption |
| 4 | authoring |
Authoring |
| 5 | lifecycle |
Lifecycle |
| 6 | harness-engineering |
Harness engineering |
| 7 | reference |
Reference |
| 8 | architecture-adr |
Architecture & ADRs |
This enumeration is the closed set of permitted section values. Adding,
removing, or reclassifying a section requires a delta-spec amendment to spec
0027 (spec 0027 R10); it is not a routine documentation edit.
Per-page metadata block
Each documentation page carries its metadata in a single HTML comment — the metadata block. The block is invisible when the page is rendered on github.com or by any standard Markdown renderer, yet it is trivially machine-parseable. YAML frontmatter is deliberately not used: github.com renders frontmatter as a visible table, which would regress the in-repo reading experience for every page.
Placement
The metadata block appears on its own line, immediately after the page H1.
Some pages open with an extraction banner comment on line 1 (for example
<!-- Extracted from AGENTS.md. -->) followed by the H1; the metadata block
still goes after the H1, regardless of any preceding banner. There is at
most one metadata block per page.
Grammar
The block is exactly one HTML comment of this form:
- The literal sentinel is
crewrig-doc:. The parser keys on this sentinel; a comment that does not begin with it is ignored. - Each entry is a
key=valuepair. Pairs are separated by one or more spaces. Key order is not significant — the parser sorts keys into a canonical order before emitting the index. - A value is either a bare token or a double-quoted string:
- A bare token contains no spaces and no double-quote characters
(for example
section=lifecycle,nav_order=20,published=true). - A double-quoted string is wrapped in
"and may contain spaces (for exampletitle="Plan format and review"). A value that contains a space MUST be double-quoted; an unquoted value that contains a space is a malformed block.
- A bare token contains no spaces and no double-quote characters
(for example
- The characters
"and>are FORBIDDEN inside any value, quoted or not. A>would prematurely close the surrounding HTML comment and break every renderer; a"would break quoting. The generator rejects a block whose values contain either character. - A
published=truepage whose block is missing any required field, or carries an unknownsectionvalue, is a hard error: the generator fails rather than silently dropping the page (spec 0027 Scenario 4).
Fields
| Field | Required | Permitted values | Meaning |
|---|---|---|---|
title |
yes | quoted string or bare token | Human-readable navigation title. A title containing spaces MUST be double-quoted. |
section |
yes | one of the eight section values above |
The top-level section the page belongs to. |
nav_order |
yes | non-negative integer | Position within the section's reading order (ascending). |
published |
yes | true or false |
Whether the page is part of the public documentation set. |
A page with published=false still declares the block, but only published
is required for it; section, nav_order, and title may be omitted. An
unpublished page never appears in the generated index (spec 0027 R4) yet
remains in the repository for contributors (spec 0027 R12 — unassigned files
default to unpublished).
Example
# Plan format and review
The generated index — docs/index.json
docs/index.json is the single machine-readable manifest of the public
documentation set. It is generated by scripts/build-docs-index.sh from the
per-page metadata blocks and committed to the repository, so a separate
site repository can fetch it by URL alone — without cloning CrewRig or running
any CrewRig tooling (spec 0027 R5, R9).
Schema
{
"version": 1,
"sections": [
{
"section": "reference",
"title": "Reference",
"pages": [
{
"title": "CLI support matrix",
"path": "docs/cli-matrix.md",
"nav_order": 10
}
]
}
]
}
version— manifest schema version. Currently1.sections— array of sections that contain at least one published page, emitted in the fixed eight-section order above. Empty sections are omitted.- For each section:
section(the enum value),title(the human-readable title from the table above), andpages. pages— published pages in that section, sorted bynav_orderascending, then bypathlexicographically as a deterministic tie-breaker. Each page carriestitle,path(repository-root-relative POSIX path), andnav_order.
The emitter is deterministic: stable key order, fixed section order,
stable page sort, and a trailing newline. Re-running the generator on an
unchanged tree produces a byte-identical file, which is what makes the
--check drift guard reliable.
Consuming the manifest from a site repository
A separate site repository (for example crewrig-website) renders
crewrig.org/docs from docs/index.json alone:
- Fetch
docs/index.jsonby raw URL from the CrewRig repository at the pinned ref. - Walk
sectionsin array order to build the navigation tree; within each section, walkpagesin array order. - For each page, fetch the Markdown body at
path(also by raw URL), strip the metadata block (the HTML comment is inert to renderers anyway), and render the body.
The site repository never needs to know CrewRig's internal directory layout: the manifest is the contract. The metadata-block grammar above is the only CrewRig-internal detail a site build must understand if it chooses to read page blocks directly rather than relying on the manifest.
The generator — scripts/build-docs-index.sh
scripts/build-docs-index.sh scans docs/**/*.md, parses each page's
metadata block, and emits docs/index.json.
| Invocation | Behavior |
|---|---|
bash scripts/build-docs-index.sh |
Regenerate docs/index.json in place. |
bash scripts/build-docs-index.sh --check |
Regenerate to a temp file, diff against the committed docs/index.json, exit non-zero on drift. Also runs the lint pass. |
The lint pass (run under --check, and on every default run before
writing) fails on any of:
- A
published=truepage missing a required field (title,section,nav_order, orpublished). - A page declaring an unknown
section(not one of the eight). - A value containing a forbidden
"or>character. - An orphan in either direction: a
docs/index.jsonentry whosepathdoes not resolve to a file, or a published page absent from the committed manifest (drift).
CI runs bash scripts/build-docs-index.sh --check in the check-components
job of .github/workflows/build.yml, mirroring the component-drift guard.
Organization overlay
An adopting organization adds its own documentation pages under docs/org/
using the identical metadata block. Those pages are excluded from
upstream sync (.crewrig/core-paths.txt), so they never flow back to CrewRig
(spec 0027 R7). The organization's own site build unions the core manifest
(docs/index.json) with an overlay manifest it generates over docs/org/**,
rendering core and overlay pages under one navigation tree against the same
contract (spec 0027 R8). See docs/org/README.md.