From 7cd5f6680fef9f78855bd37e87ee4b3501115916 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Wed, 24 Jun 2026 07:36:28 -0600 Subject: [PATCH] chore(hooks): auto-regenerate the primitive catalog on pre-commit Extends the biome pre-commit hook to also regenerate docs/api/primitive-catalog.md when a staged change adds/removes an export under src/ (or edits the package.json exports map): rebuild + regenerate + re-stage, so the docs:check CLASS-7 freshness gate self-heals at commit time instead of red-building in CI. No-ops on irrelevant changes, skips in CI, bypass with SKIP_DOCS_REGEN=1. --- .githooks/pre-commit | 41 +++++++++++++++++++++++++++++++++-------- docs/MAINTAINING.md | 8 ++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 58bdca0..11655e6 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,9 +1,34 @@ #!/usr/bin/env sh -# Auto-fix biome (lint + format) on staged files, then re-stage them so the -# commit lands clean. No-op when biome isn't installed or nothing relevant is -# staged. Activated by `prepare` (git config core.hooksPath .githooks). -[ -x ./node_modules/.bin/biome ] || exit 0 -files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx|js|jsx|json|jsonc)$' || true) -[ -z "$files" ] && exit 0 -printf '%s\n' "$files" | xargs ./node_modules/.bin/biome check --write --no-errors-on-unmatched -printf '%s\n' "$files" | xargs git add +# Pre-commit, two independent steps, each re-stages so the commit lands clean: +# (1) auto-fix biome (lint + format) on staged files. +# (2) regenerate the generated primitive catalog when staged changes touch +# exports — the self-healing companion to the docs:check CLASS-7 freshness +# gate, so a forgotten regen never red-builds CI. +# Activated by `prepare` (git config core.hooksPath .githooks). No-op when a +# step's tools/inputs are absent. + +# (1) biome auto-fix on staged source files. +if [ -x ./node_modules/.bin/biome ]; then + files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(ts|tsx|js|jsx|json|jsonc)$' || true) + if [ -n "$files" ]; then + printf '%s\n' "$files" | xargs ./node_modules/.bin/biome check --write --no-errors-on-unmatched + printf '%s\n' "$files" | xargs git add + fi +fi + +# (2) regenerate docs/api/primitive-catalog.md when a staged change adds/removes +# an export under src/ or edits the package.json `exports` map (the only things +# that change the generated inventory). Skipped in CI (docs:check runs there) and +# when nothing export-relevant is staged. `SKIP_DOCS_REGEN=1` bypasses it. +if [ "$CI" != "true" ] && [ "$SKIP_DOCS_REGEN" != "1" ]; then + exports_changed=$(git diff --cached --diff-filter=ACMR -- src | grep -E '^[+-]' | grep -vE '^[+-][+-][+-]' | grep -E 'export[[:space:]]+(const|function|class|type|interface|enum|namespace|abstract|declare|default|async|let|var)|export[[:space:]]*[*{]' || true) + pkg_exports_changed=$(git diff --cached --diff-filter=ACMR -- package.json | grep -E '^[+-]' | grep -vE '^[+-][+-][+-]' | grep -E '"exports"|"\./' || true) + if [ -n "$exports_changed" ] || [ -n "$pkg_exports_changed" ]; then + printf 'pre-commit: exports changed — regenerating primitive catalog (builds; set SKIP_DOCS_REGEN=1 to skip)\n' + # The catalog generator resolves the own surface through dist, so build first. + # Fail loud: a stale/empty catalog would defeat the gate it exists to satisfy. + pnpm run build >/dev/null 2>&1 || { printf 'pre-commit: build failed — cannot regenerate the catalog (fix the build, or SKIP_DOCS_REGEN=1)\n' >&2; exit 1; } + node scripts/gen-primitive-catalog.mjs >/dev/null 2>&1 || { printf 'pre-commit: catalog regeneration failed\n' >&2; exit 1; } + git add docs/api/primitive-catalog.md + fi +fi diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md index b070139..13e62a7 100644 --- a/docs/MAINTAINING.md +++ b/docs/MAINTAINING.md @@ -122,6 +122,14 @@ to regenerate after a TSDoc change), then runs the freshness gate. CI runs this last step of the `ci` job (`.github/workflows/ci.yml`); drift is a red build, not a silent lie. +**The pre-commit hook (`.githooks/pre-commit`) is the gate's self-healing companion.** +When a staged change adds/removes an export under `src/` (or edits the `package.json` +`exports` map), the hook rebuilds and regenerates `docs/api/primitive-catalog.md` and +re-stages it, so the commit already carries a fresh catalog and you don't hit the "forgot +to regenerate" red build. It no-ops on changes that can't affect the catalog, skips in CI +(where `docs:check` runs regardless), and is bypassable with `SKIP_DOCS_REGEN=1`. The gate +catches drift; the hook prevents it. + The `git diff --exit-code -- docs/api` staleness catch relies on the **tracked per-module README index** (`docs/api//README.md`): every new export — even one with no TSDoc — adds a link line to that index, dirtying a tracked file. `git diff`