CI-mirror git hooks block red builds before push¶
Motivation¶
Two GitHub Actions workflows gate main — Tests
(.github/workflows/test.yml: ruff check api
+ pytest -q api/tests) and Publish Docs
(.github/workflows/docs.yml: frontmatter
canon guard + mkdocs build --strict). Recent pushes turned the dashboard red
twice for avoidable reasons: a doc page added without a nav: entry (fails
--strict) and a flaky subprocess test. Nothing reproduced the exact CI checks
locally before a push, so the failures were only visible after the fact.
User-facing change¶
A version-controlled git-hook set now mirrors both gates on the developer's machine, opt-in via a single installer:
- pre-commit (fast, staged files only): runs
ruff check apiwhenapi/**is staged and the docs frontmatter guard whendocs/**/mkdocs.ymlis staged. ~2 s. - pre-push (full CI mirror): runs
pytest -q api/testsand/ormkdocs build --strict, each only when the pushed commit range actually touches the paths that the corresponding workflow gates on (a docs-only push skips pytest and vice-versa). Branch deletions are a true no-op.
Bypass for genuine emergencies with git commit/push --no-verify or
ELB_SKIP_HOOKS=1. Uninstall with git config --unset core.hooksPath.
The agent charter (.github/copilot-instructions.md) gains a "CI parity" subsection under Validation before marking done that documents the workflow→local-command mapping and mandates installing the hooks.
File diff summary¶
| File | Change |
|---|---|
scripts/dev/git-hooks/_lib.sh |
New — path classification (paths_touch_api / paths_touch_docs, mirroring the workflows' paths: filters) + labelled hook_run logging. |
scripts/dev/git-hooks/pre-commit |
New — staged-file ruff + frontmatter guard. |
scripts/dev/git-hooks/pre-push |
New — pushed-range pytest + mkdocs build --strict, scoped per touched paths; deletion-only push is a no-op. |
scripts/dev/install-git-hooks.sh |
New — idempotent installer that sets core.hooksPath. |
.github/copilot-instructions.md |
Added the "CI parity" subsection. |
scripts/dev/README.md |
Documented the installer and git-hooks/ directory. |
No API or IaC surface changes.
Validation evidence¶
bash scripts/dev/install-git-hooks.sh→core.hooksPath=scripts/dev/git-hooks.- pre-commit on a staged
mkdocs.yml: ran the frontmatter guard,✓, exit 0. - pre-push deletion-only stdin (
local_sha=0000…): true no-op, exit 0. - pre-push on a real range: correctly ran
ruff+pytestand blocked on a failing test (assert 124 == 0), proving the gate works end-to-end. - Path classification spot-check:
api/main.py→ api=Y docs=n;docs/foo.md→ api=n docs=Y;mkdocs.yml→ docs=Y;web/src/App.tsx→ both n. bash -nclean on all three hook scripts.