Files
settled-reach/.config/hooks/pre-commit
T
jpmschweitzerandClaude Opus 5 cb5d3f1335 refactor(config): T-1281 — the check domain retires its five scripts
All five gates are ported, tested against their failure paths, and the
originals are gone. reach check is the only way to run them.

Parity first, then deletion. Every case in test_check.py began as a parity case
running the new implementation beside the script it replaced; that evidence is
in the ticket. With the scripts retired there is nothing left to compare
against, so the assertions become the spec and the file drops its "_parity"
name. A parity test is scaffolding with a defined lifetime — keeping one after
its subject is deleted would mean keeping the subject alive to be compared
with, which is the opposite of a migration.

Two gates could not be parity-tested in a fixture at all, and both reasons are
findings rather than obstacles. canvas-version: canvas_sources globs from a
__file__ root while the service resolves git through config.repo_root(), so a
fixture would diff one tree and glob another — real history is used instead,
including two genuine instances of the regression the gate exists to catch.
systems-db-stamp: generator_sources raises at IMPORT time when the economy-db
tree is absent, so the old script died before reaching any logic in every
fixture. The ported service imports it lazily and after the absent/unstamped
checks, which is exactly why those states are testable now and were not before.

Hooks rewired: pre-commit runs reach check fact-ids, pre-push runs the other
four. Both pass --no-input, because a hook has no TTY and a prompt there does
not wait, it crashes. Both guard on `command -v reach` and skip with a message
rather than blocking every commit on a missing tool.

Make targets are RETIRED, not wrapped, per the D-263 split — with the mapping
left as a comment where they used to be. Wrapping would leave two ways to
invoke each gate, and reach --help would stop being the answer to "what tooling
exists" while the Makefile remained a competing index. pre-pr-validate and
pre-pr-content keep their orchestration role and lose the individual target.

Sprint archives and workshop notes still name the old paths and are left alone:
they record what was true when written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:33 +02:00

73 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# pql: source .pql/hooks/pre-commit
_pql_hook="$(git rev-parse --show-toplevel)/.pql/hooks/pre-commit"; [ -f "$_pql_hook" ] && . "$_pql_hook"
# Pre-commit hook dispatcher. Runs modular checks from tooling/.
# Installed via: git config core.hooksPath .config/hooks
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
ERRORS=0
run_check() {
local script="$1"
local label="$2"
if [ -x "$REPO_ROOT/$script" ]; then
if ! "$REPO_ROOT/$script"; then
ERRORS=$((ERRORS + 1))
fi
else
echo "pre-commit: WARNING — $label skipped ($script not found or not executable)"
echo " Run 'make setup' or check that $script exists and is executable."
fi
}
# --- Checks ---
# Ported to the reach CLI (T-1281). --no-input because a hook has no TTY and a
# prompt there does not wait, it crashes. If reach is missing the check is
# skipped with a message rather than blocking every commit on a tooling install.
if command -v reach >/dev/null 2>&1; then
if ! reach --no-input check fact-ids; then
ERRORS=$((ERRORS + 1))
fi
else
echo "pre-commit: WARNING — fact_id validation skipped (reach not on PATH)"
echo " Run 'make install-reach'."
fi
# --- pql: decision integrity + durable planning changelog ---
# Replaces the old SQLite decisions-sync hook. Decisions are markdown-sourced (D-8):
# `decisions validate` is the decision-ID/format gate (supersedes the never-built
# check-decision-ids), and `plan export --stage` flushes any ticket mutations to the
# git-tracked .pql/changelog/ and stages them so planning state lands in this commit.
# pql installs its own hooks into .pql/hooks, which git ignores under
# core.hooksPath=.config/hooks — so the logic is folded in here by hand instead.
if command -v pql >/dev/null 2>&1; then
if ! pql decisions validate >/dev/null; then
echo "pre-commit: pql decisions validate failed — malformed decision record(s)."
ERRORS=$((ERRORS + 1))
fi
pql plan export --stage >/dev/null 2>&1 || \
echo "pre-commit: WARNING — pql plan export failed; changelog not refreshed."
else
echo "pre-commit: WARNING — pql not on PATH; decision validate + changelog export skipped."
fi
# Run cargo audit only when Cargo.toml or Cargo.lock changed
if git diff --cached --name-only | grep -qE '(Cargo\.toml|Cargo\.lock)$'; then
echo "pre-commit: Cargo dependency change detected — running cargo audit..."
if command -v cargo-audit >/dev/null 2>&1 || cargo audit --version >/dev/null 2>&1; then
if ! (cd "$REPO_ROOT/server" && cargo audit); then
ERRORS=$((ERRORS + 1))
fi
else
echo "pre-commit: WARNING — cargo-audit not installed, skipping advisory check"
echo " Install with: cargo install cargo-audit"
fi
fi
if [ "$ERRORS" -gt 0 ]; then
echo ""
echo "pre-commit: $ERRORS check(s) failed. Commit aborted."
exit 1
fi