diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..0f694c8 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# pre-push: the Lens pass. Runs `make verify` (cheap gate + heavy sensors + both +# test suites) and blocks the push if anything fails. +# +# Install with `make hooks` (symlinks this file into this clone's git hooks +# directory). Deliberate bypass: `git push --no-verify`. +# +# Why a symlink and not `git config core.hooksPath .githooks`: bd keeps its own +# managed hooks in .git/hooks (pre-commit flushes .beads/issues.jsonl, post-merge +# imports it). Pointing core.hooksPath elsewhere would silently retire them, and +# bd would still report its hooks as installed. Installing one symlink per hook +# in .githooks/ keeps every hook manager's files where they expect them. +# +# stdin carries " " per ref being +# pushed; the Lens pass is repo-wide, so it is deliberately not read. + +set -uo pipefail + +if ! cd "$(git rev-parse --show-toplevel)"; then + echo "pre-push: cannot find the repository root; refusing to let this push through" >&2 + exit 1 +fi + +if ! command -v make >/dev/null 2>&1; then + echo "pre-push: make not found on PATH — the Lens pass cannot run, so the push is blocked" >&2 + echo "pre-push: install make, or bypass deliberately with: git push --no-verify" >&2 + exit 1 +fi + +log=$(git rev-parse --git-dir)/lens-pass.log +echo "pre-push: running the Lens pass (make verify) — full log: $log" + +set -o pipefail +if make verify 2>&1 | tee "$log"; then + echo "pre-push: Lens pass passed" + exit 0 +fi + +last_step=$(grep -E '^==> ' "$log" | tail -n 1) +{ + echo + echo "==================================================================" + echo "pre-push: LENS PASS FAILED — push blocked" + echo " last step started: ${last_step:-unknown (see log)}" + echo " full log: $log" + echo " triage the failure (real bug / environment-dependent test /" + echo " design nit — see AGENTS.md), then re-run: make verify" + echo " deliberate bypass: git push --no-verify" + echo "==================================================================" +} >&2 +exit 1 diff --git a/Makefile b/Makefile index 208caf1..8a86275 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ MISE_BIN ?= mise -.PHONY: check precommit deps compile test test-elisp format verify +.PHONY: check precommit verify hooks deps compile test test-elisp format # Cheap static gate over both apps, cheapest first. No database, no coverage: # this is the loop you run freely while working and before every commit. @@ -27,6 +27,23 @@ verify: check @echo "==> verify: blogex" @$(MAKE) -C blogex MISE_BIN=$(MISE_BIN) verify +# Install the versioned hooks in .githooks/ into this clone's hooks directory as +# symlinks (edits take effect immediately, no copy to drift). Idempotent; re-run +# after moving the clone, since the links are absolute. +# +# core.hooksPath is deliberately NOT changed: bd manages its own hooks inside +# .git/hooks (pre-commit flushes .beads/issues.jsonl, post-merge imports it) and +# moving the hooks path would silently retire them. +hooks: + @hooks_dir=$$(git rev-parse --git-path hooks); \ + root=$$(git rev-parse --show-toplevel); \ + mkdir -p "$$hooks_dir"; \ + for hook in .githooks/*; do \ + name=$$(basename "$$hook"); \ + ln -sfn "$$root/$$hook" "$$hooks_dir/$$name"; \ + echo "installed $$hooks_dir/$$name -> $$hook"; \ + done + # Sync dependencies deps: @make -C app deps