Skip to content

Docs-Site Lifecycle (#153, #689)

Provision and operate the engine-pluggable docs site. Extracted from the dev lifecycle reference.

← docs home

Every BoB-provisioned project can host a docs site that reads its own docs/, README.md, and CLAUDE.md. The lifecycle is orthogonal to the dev environment lifecycle but follows the same declarative pattern.

The tooling is engine-pluggable (#689). The engine field in docs-site.json selects the static-site generator:

Engine Value Notes
VitePress (default) vitepress Flat .html with native .md link rewriting — correct under flat Cloudflare-Pages serving with no post-build step. Folder-autogenerated Diátaxis sidebar (vitepress-sidebar), built-in local (MiniSearch) search.
Astro Starlight (alternative) starlight build.format: 'file' + trailingSlash: 'never' for the same flat serving. Folder-autogenerated nav, Pagefind search, richest theming.

Both render the same docs/ taxonomy and resolve cross-directory links correctly under flat serving with no post-build link rewriting. Quartz — and its fix-links.js hack — was retired in #689 (building on the #487 SSG research).

Accepted regression: Quartz’s graph view and automatic backlinks have no equivalent in either engine; [[wikilinks]] need a plugin or a one-time conversion.

Node requirement: the Starlight engine (Astro 7) needs Node ≥ 22.12; VitePress is fine on Node ≥ 18. CI pins Node 22 in docs-deploy.yml for this reason.

Sub-command Script Purpose
/docs-site init ~/.claude/scripts/docs-site/init.sh Scaffold .docs-site/<engine>/ for the resolved engine + a default docs-site.json (if missing). Idempotent on re-run. --engine forces an engine.
/docs-site build ~/.claude/scripts/docs-site/build.sh Sync the docs/ taxonomy + run the engine build. Flat output at .docs-site/public/. --engine overrides.
/docs-site dev ~/.claude/scripts/docs-site/dev.sh Sync content + engine dev server with hot reload. Binds the project’s docs port from the port ledger (base+3), so it never collides with the app or another project. --port overrides; --engine overrides the engine; --print-port resolves and prints the port without starting a server.
/docs-site deploy ~/.claude/scripts/docs-site/deploy.sh Build, then publish to the configured target (cloudflare-pages, static, custom). The primary engine deploys to deploy.project; a comparison engine deploys to <deploy.project>-<engine>.
/docs-site clean ~/.claude/scripts/docs-site/clean.sh Drop build artifacts (preserves installed deps). --hard removes the whole .docs-site/ workspace.

Per-project config (docs-site.json at project root):

{
"engine": "vitepress",
"title": "Bodmail Docs",
"description": "Documentation for the Bodmail email platform",
"base_url": "https://docs.bodmail.app",
"sources": ["docs/", "README.md", "CLAUDE.md"],
"exclude": ["docs/archive/**", "docs/audits/**"],
"deploy": { "target": "cloudflare-pages", "project": "bodmail-docs" }
}

base_url drives the serve path. The engine base is the path component of base_url, normalised to /…// for a root-served Pages project (the BoB default, empty/host-only base_url), or e.g. /docs/ only if the site is genuinely served under that subpath. A base that doesn’t match where the site is actually served makes every asset 404 (an unstyled page). The flat-link gate (make docs-verify) runs at this same derived base.

Adopting in a project:

  1. cd /path/to/project && /docs-site init — scaffolds .docs-site/<engine>/ and a default docs-site.json.
  2. Edit docs-site.json — set the engine, title, base URL, and deploy target.
  3. /docs-site dev — preview at the printed URL (the project’s ledger docs port; run /docs-site dev --print-port to see it).
  4. /docs-site deploy when ready.

Trying the other engine, ad hoc (no config change): /docs-site build --engine starlight or /docs-site dev --engine starlight.

Flat-serving link gate (make docs-verify): builds both engines against the real docs/ and asserts every in-docs cross-directory link resolves under flat Cloudflare serving with no rewriting (#689 AC-04; scripts/docs-site/verify-engines.sh + verify-links.js). Links pointing outside the published docs (repo source) are skipped, not failed.

Universal portability: the scripts read --root, $PROJECT_ROOT, or cwd — they never hardcode BOB_SOURCE. The .docs-site/<engine>/ workspace is entirely generated from templates/docs-site/<engine>/ + the synced docs/, so the whole directory is gitignored.

Auto-deploy on doc changes (#155). A GitHub Actions workflow at .github/workflows/docs-deploy.yml rebuilds, verifies, and publishes the site whenever a published doc source changes (docs/**, README.md, CLAUDE.md, docs-site.json, the docs-site scripts, or the engine templates). On master push it deploys both engines for a live A/B; on PRs it deploys previews and posts the URLs back as a sticky comment.

The pipeline is:

checkout → cache .docs-site/{vitepress,starlight}/node_modules
→ make docs-check (high-severity drift fails the build, blocks deploy)
→ verify-engines.sh (build + flat-link-verify BOTH engines)
→ build vitepress → wrangler pages deploy → bigbrain-docs (primary)
→ build starlight → wrangler pages deploy → bigbrain-docs-starlight (comparison, best-effort)
→ comment preview URLs on the PR

Required repo configuration:

  • Secret CLOUDFLARE_API_TOKEN — Cloudflare API token with Pages:Edit scope.
  • Secret CLOUDFLARE_ACCOUNT_ID — the Cloudflare account UUID.
  • Variable DOCS_SITE_DEPLOY_ENABLED=true — feature flag that gates the wrangler steps. Until set, the workflow still builds, verifies, and runs the drift gate (catching breakage) but skips the deploy.
  • A one-time bigbrain-docs-starlight Cloudflare Pages project for the comparison build — the Starlight deploy is best-effort, so a missing project never blocks the primary production deploy. See #187 for the Cloudflare connection step.

NPM script aliases live in the root package.json so users can run npm run docs:build / docs:dev / docs:deploy from anywhere in the repo.

Every /doc-audit run writes two artifacts to docs/audits/:

  • doc-audit-YYYY-MM-DD.{md,json} — date-stamped historical record
  • latest.{md,json} — canonical aliases the docs site reads

The site (when built via /docs-site build) consumes docs/audits/latest.md for a “Docs last verified” badge on the landing page and an “Audits” entry in the nav. latest.json is exposed as a downstream API endpoint at /audits/latest.json. See docs/audits/ for the canonical-path contract and history convention.

The nav + landing-badge component wiring lands as a follow-up — the data plumbing (latest aliases + history) is in place.