Files
gastownhall__beads/scripts/generate-cli-docs.sh
Chris Sells d2f87d50de docs(pipeline): pin the CLI docs to the released bd; repair main's broken CLI reference (#4876)
* docs(pipeline): pin the generated CLI docs to the released bd; repair main's CLI reference

The #4874 squash-merge raced the docs-autofix bot: the bot had pushed a
regeneration from main's source onto the PR branch, so the squash landed a
contradictory mix — the release-aligned hand-written docs plus a CLI
reference re-documenting unreleased commands and flags, with docs.json
referencing four pages (heartbeat, migrate-personal, reclaim, unclaim)
whose files do not exist. That broken nav fails the docsync guard on main
and on every branch cut from it. This restores the intended end state of
#4874 and lands the pin that prevents the race from recurring.

- Regenerate the CLI reference from a bd built from the v1.1.0 tag (CI's
  canonical pure-Go build), removing the unreleased commands/flags and
  fixing the dangling nav entries.
- docs/cli-docs.pin names the release tag; scripts/resolve-docs-bd.sh
  builds and caches a canonical pure-Go bd from that tag, fetching the tag
  first in shallow CI checkouts.
- generate-cli-docs.sh, check-doc-flags.sh, and check-cli-docs-drift.sh
  resolve bd through the pin and ignore a supplied binary while pinned
  (BD_DOCS_IGNORE_PIN=1 bypasses), so the drift gates and the autofix bot
  now enforce docs-match-release instead of docs-match-main.
- tools/docsmint detects the pre-overhaul Docusaurus staging layout that
  bd <= v1.1.0 emits and converts it to the generic form before its normal
  Mintlify transform (tested); removable once the pin reaches a release
  whose emitter writes build/cli-docs.
- check-doc-flags.sh: run without timeout(1) on macOS instead of failing
  Check 4.
- Decision record: engdocs/decisions/2026-07-17-docs-release-pin.md; the
  beads-docs skill §8 updated to match.

* lint: annotate gosec G705 false positive in the docsmint legacy bridge

Same class as the existing nav-splice annotation: a developer tool writing
repo Markdown, not a web-output sink.
2026-07-17 18:03:28 -07:00

192 lines
7.9 KiB
Bash
Executable File

#!/bin/bash
# generate-cli-docs.sh — Generate CLI reference docs from the live bd command tree.
#
# Two stages:
# 1. `bd help --docs-root <root>` emits vendor-neutral output only:
# docs/CLI_REFERENCE.md plus a generic per-command tree at
# build/cli-docs/ (uncommitted staging).
# 2. `go run ./tools/docsmint <root>` post-processes the staging tree into
# the committed Mintlify pages at docs/cli-reference/ and splices the
# CLI Reference pages array in docs/docs.json.
# bd never emits site-generator-specific formats; all Mintlify targeting
# lives in tools/docsmint.
set -euo pipefail
export LC_ALL=C
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
CHECK_MODE=0
BD_ARG=""
TMP_BUILD_DIR=""
TMP_OUTPUT_DIR=""
while [ "$#" -gt 0 ]; do
case "$1" in
--check)
CHECK_MODE=1
shift
;;
-h|--help)
cat <<'EOF'
Usage: scripts/generate-cli-docs.sh [--check] [path-to-bd]
Generate the committed CLI docs (docs/CLI_REFERENCE.md, docs/cli-reference/,
and the CLI pages array in docs/docs.json) from one bd process plus the
tools/docsmint post-processor. --check regenerates into a scratch root and
fails if the committed copies are stale.
When docs/cli-docs.pin names a release tag, bd is built from that tag
(the published docs describe the pinned release, not this checkout) and a
supplied binary is ignored; set BD_DOCS_IGNORE_PIN=1 to bypass the pin.
If the resolved bd binary is CGO-enabled it emits the full `bd federation`
help tree that CI (CGO_ENABLED=0) stubs out; the script rebuilds a pinned
pure-go binary to keep committed docs in sync. Set BD_DOCS_ALLOW_CGO=1 to
bypass that rebuild and trust the supplied binary as-is.
EOF
exit 0
;;
*)
if [ -n "$BD_ARG" ]; then
echo "Error: multiple bd binaries supplied: $BD_ARG and $1" >&2
exit 2
fi
BD_ARG="$1"
shift
;;
esac
done
cleanup() {
if [ -n "$TMP_BUILD_DIR" ]; then
rm -rf "$TMP_BUILD_DIR"
fi
if [ -n "$TMP_OUTPUT_DIR" ]; then
rm -rf "$TMP_OUTPUT_DIR"
fi
}
trap cleanup EXIT
# Release pin (docs/cli-docs.pin): the published docs describe the pinned
# release, so the pipeline builds bd from that tag and ignores any supplied
# binary. Set BD_DOCS_IGNORE_PIN=1 to bypass (e.g. to preview docs for an
# unreleased emitter).
PINNED_BD=""
if [ "${BD_DOCS_IGNORE_PIN:-0}" != "1" ]; then
PINNED_BD="$("$SCRIPT_DIR/resolve-docs-bd.sh")"
fi
if [ -n "$PINNED_BD" ]; then
if [ -n "$BD_ARG" ] && [ "$BD_ARG" != "$PINNED_BD" ]; then
echo "Note: docs are pinned via docs/cli-docs.pin; ignoring supplied binary $BD_ARG." >&2
fi
BD="$PINNED_BD"
elif [ -n "$BD_ARG" ]; then
BD="$BD_ARG"
elif [ "$CHECK_MODE" -eq 0 ] && [ -x "$PROJECT_ROOT/bd" ]; then
# Convenience for regeneration only. --check never trusts a repo-root
# ./bd it wasn't explicitly given: the guard below detects CGO-ness, not
# staleness, so a stale pure-go ./bd would produce a false "fresh".
BD="$PROJECT_ROOT/bd"
else
TMP_BUILD_DIR="$(mktemp -d)"
BD="$TMP_BUILD_DIR/bd"
echo "Building temporary bd for docs generation..."
(cd "$PROJECT_ROOT" && CGO_ENABLED=0 go build -tags gms_pure_go -o "$BD" ./cmd/bd/)
fi
# Guard against a CGO-enabled bd: it exposes the full `bd federation` subcommand tree
# that CI never produces (scripts/ci/pr-policy.sh build_docs_binary uses env
# CGO_ENABLED=0 go build). A CGO build therefore emits ~hundreds of lines of federation
# help that the committed, CI-built docs stub out ("built without CGO support"), so a
# naive regen on a machine with a C compiler produces spurious federation churn.
#
# The pure-go federation stub prints "Federation commands require CGO" (see
# cmd/bd/federation_nocgo.go); a CGO build does not. If the resolved binary is missing
# that stub marker, warn and rebuild a pinned CGO_ENABLED=0 -tags gms_pure_go binary so
# the committed docs always match CI. Set BD_DOCS_ALLOW_CGO=1 to bypass the rebuild and
# trust the supplied binary as-is (e.g. to deliberately regenerate the full federation
# tree); you then own any federation churn the diff introduces.
if [ -z "$PINNED_BD" ] && [ -x "$BD" ] && ! "$BD" federation --help 2>&1 | grep -q "Federation commands require CGO"; then
if [ "${BD_DOCS_ALLOW_CGO:-0}" = "1" ]; then
echo "WARNING: $BD looks CGO-enabled and emits the full 'bd federation' help tree," >&2
echo " which CI's pure-go docs build stubs out. BD_DOCS_ALLOW_CGO=1 is set," >&2
echo " using it anyway; expect federation doc churn unless that is intended." >&2
else
echo "WARNING: $BD looks CGO-enabled; its 'bd federation' help differs from CI's" >&2
echo " pure-go build and would add spurious federation doc churn." >&2
echo " Rebuilding a pinned pure-go (CGO_ENABLED=0 -tags gms_pure_go) binary" >&2
echo " for CI-consistent docs. Set BD_DOCS_ALLOW_CGO=1 to use it as-is." >&2
if [ -z "$TMP_BUILD_DIR" ]; then
TMP_BUILD_DIR="$(mktemp -d)"
fi
BD="$TMP_BUILD_DIR/bd-pure"
(cd "$PROJECT_ROOT" && CGO_ENABLED=0 go build -tags gms_pure_go -o "$BD" ./cmd/bd/)
fi
fi
if [ ! -x "$BD" ]; then
echo "Error: bd binary not found or not executable: $BD" >&2
echo "Usage: $0 [--check] [path-to-bd]" >&2
exit 1
fi
generate_all() {
local root="$1"
local had_website=0
[ -d "$root/website" ] && had_website=1
# Clear stale staging so docsmint can only consume what this bd emitted
# (a pinned legacy bd stages under website/, not build/cli-docs).
rm -rf "$root/build/cli-docs"
"$BD" help --docs-root "$root"
(cd "$PROJECT_ROOT" && go run -tags=gms_pure_go ./tools/docsmint "$root")
# A pinned legacy emitter (bd <= v1.1.0) stages Docusaurus pages under
# website/; docsmint has consumed them, so drop the scaffolding.
if [ "$had_website" -eq 0 ] && [ -d "$root/website" ]; then
rm -rf "$root/website"
fi
}
if [ "$CHECK_MODE" -eq 1 ]; then
TMP_OUTPUT_DIR="$(mktemp -d)"
mkdir -p "$TMP_OUTPUT_DIR/docs"
# Seed the committed artifacts the pipeline rewrites, then regenerate and diff.
cp -f "$PROJECT_ROOT/docs/docs.json" "$TMP_OUTPUT_DIR/docs/docs.json"
if [ -d "$PROJECT_ROOT/docs/cli-reference" ]; then
cp -Rf "$PROJECT_ROOT/docs/cli-reference" "$TMP_OUTPUT_DIR/docs/cli-reference"
fi
generate_all "$TMP_OUTPUT_DIR"
if ! diff -q \
"$PROJECT_ROOT/docs/CLI_REFERENCE.md" \
"$TMP_OUTPUT_DIR/docs/CLI_REFERENCE.md" >/dev/null; then
echo "FAIL: docs/CLI_REFERENCE.md is out of sync with live CLI help."
echo "Run: ./scripts/generate-cli-docs.sh ${BD_ARG:-}"
diff -u "$PROJECT_ROOT/docs/CLI_REFERENCE.md" "$TMP_OUTPUT_DIR/docs/CLI_REFERENCE.md" | sed -n '1,120p' || true
exit 1
fi
if ! diff -q "$PROJECT_ROOT/docs/docs.json" "$TMP_OUTPUT_DIR/docs/docs.json" >/dev/null; then
echo "FAIL: docs/docs.json CLI Reference navigation is out of sync with live CLI help."
echo "Run: ./scripts/generate-cli-docs.sh ${BD_ARG:-}"
diff -u "$PROJECT_ROOT/docs/docs.json" "$TMP_OUTPUT_DIR/docs/docs.json" | sed -n '1,80p' || true
exit 1
fi
if ! diff -qr "$PROJECT_ROOT/docs/cli-reference" "$TMP_OUTPUT_DIR/docs/cli-reference" >/dev/null; then
echo "FAIL: docs/cli-reference is out of sync with live CLI help."
echo "Run: ./scripts/generate-cli-docs.sh ${BD_ARG:-}"
diff -ur "$PROJECT_ROOT/docs/cli-reference" "$TMP_OUTPUT_DIR/docs/cli-reference" | sed -n '1,160p' || true
exit 1
fi
echo "PASS: generated CLI docs are fresh"
else
generate_all "$PROJECT_ROOT"
echo "Generated CLI docs from: $($BD version 2>/dev/null | head -1 || echo "$BD")"
echo "Updated docs/CLI_REFERENCE.md, docs/cli-reference/, and the docs.json CLI nav"
fi