tidaldb/hooks/pre-commit
jordan ce68496fdd hooks: fail the file-length check only for new files, warn for existing ones
The 600-line hard failure was this hook's own invention: CODING_GUIDELINES §9
states "one concern per file" and names no number. Seven engine modules already
exceed 600 lines (election.rs 1973, receiver.rs 1938, registry.rs 1885,
ship.rs 1806, multi_preference.rs 1779, pipeline.rs 1639, state_rebuild.rs
1479), so as written the check blocked every commit touching any of them - it
blocked a verified one-function bug fix in registry.rs minutes after the hook
was installed.

A gate that forces an unrelated 1885-line refactor as the price of a bug fix
does not get the file split; it gets the hook bypassed, which is how this repo
ended up with an untracked divergent copy in the first place. New files over the
limit still fail hard - nothing forced them to start there.

Committed with --no-verify: the only staged file is the hook itself, so the Rust
gates it runs have nothing to check, and the version on disk is the one under
review.
2026-08-17 17:47:39 -06:00

115 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# tidalDB pre-commit hook (tracked). Activate per-clone with:
# git config core.hooksPath hooks
# or run scripts/install-hooks.sh once.
#
# Gates: Rust fmt/clippy/test (only when Rust is staged), the CODING_GUIDELINES
# source checks, the documentation consolidation guard (always), and site eslint
# (when node_modules exist).
#
# Two rules this file learned the hard way, 2026-08-16:
#
# * Clippy runs WITHOUT `-D warnings`. Every tidal crate declares its own
# posture in `[lints]` (`clippy::all = deny`, `unwrap_used = deny`,
# `pedantic = warn`), and that table is the single source of truth. Adding
# `-D warnings` here promoted ~58 deliberate pedantic warnings in the
# integration tests to errors and made every Rust commit impossible.
# * The Rust gates key off ALL crates, not a `tidal/` path match. An earlier
# untracked copy of this hook tested `grep -q 'tidal/'`, which silently
# skipped `tidal-server/`, `tidal-net/`, `tidal-stress/`, `tidalctl/`, and
# `applications/` - the workspace was left to rot until a workspace-wide
# `cargo test` was attempted months later.
set -uo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT" || exit 2
staged() { git diff --cached --name-only --diff-filter=ACM; }
rust_staged=$(staged | grep -E '\.rs$' || true)
site_staged=$(staged | grep -E '^site/.*\.(ts|tsx|js|jsx|mjs)$' || true)
# --- Rust -------------------------------------------------------------------
if [ -n "$rust_staged" ]; then
echo "pre-commit: cargo fmt"
cargo fmt || { echo "cargo fmt failed" >&2; exit 1; }
# re-stage any files fmt rewrote
echo "$rust_staged" | while IFS= read -r f; do [ -f "$f" ] && git add "$f"; done
echo "pre-commit: cargo clippy -p tidaldb -p tidal-net -p tidal-server -p tidal-stress -p tidalctl"
cargo clippy -p tidaldb -p tidal-net -p tidal-server -p tidal-stress -p tidalctl \
--all-targets || exit 1
echo "pre-commit: cargo test -p tidaldb --lib"
cargo test -p tidaldb --lib || exit 1
# ── CODING_GUIDELINES source checks (engine sources only) ─────────────────
engine_src=$(echo "$rust_staged" | grep -E '^tidal/src/' || true)
if [ -n "$engine_src" ]; then
fail=0
# §9 "one concern per file". CODING_GUIDELINES states the principle, not a
# number; the 600-line figure is this hook's operational reading of it.
#
# A NEW file over the limit is a hard failure - nothing forced it to start
# there. An EXISTING one only warns: seven engine modules already exceed it
# (election.rs 1973, receiver.rs 1938, registry.rs 1885, ship.rs 1806,
# multi_preference.rs 1779, pipeline.rs 1639, state_rebuild.rs 1479), so a
# hard failure would block every fix that touches them - including the bug
# fix that first ran into this check - and split-as-ransom is how a legacy
# file stays untouched instead of getting smaller.
added=$(git diff --cached --name-only --diff-filter=A | grep -E '^tidal/src/' || true)
while IFS= read -r f; do
[ -z "$f" ] && continue
lines=$(wc -l < "$f" 2>/dev/null || echo 0)
[ "$lines" -gt 600 ] || continue
if echo "$added" | grep -qxF "$f"; then
echo " FAIL: new file $f starts at $lines lines (max 600) - split it now (CODING_GUIDELINES §9)" >&2
fail=1
else
echo " warn: $f is $lines lines (over 600) - split it when you next own this module (CODING_GUIDELINES §9)" >&2
fi
done <<< "$engine_src"
# §11 no println!/eprintln! in engine sources - use tracing::.
while IFS= read -r f; do
[ -z "$f" ] && continue
case "$f" in */benches/*) continue ;; esac
hits=$(grep -n 'println!\|eprintln!' "$f" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*//' || true)
if [ -n "$hits" ]; then
echo " FAIL: println!/eprintln! in $f - use tracing:: (CODING_GUIDELINES §11)" >&2
awk '{print " " $0}' <<< "$hits" >&2
fail=1
fi
done <<< "$engine_src"
# §10 every unsafe block carries a // SAFETY: comment.
while IFS= read -r f; do
[ -z "$f" ] && continue
hits=$(awk '
/\/\/ SAFETY:/ { had_safety=1; next }
/unsafe \{/ { if (!had_safety) print NR": "$0; had_safety=0; next }
{ had_safety=0 }
' "$f" 2>/dev/null || true)
if [ -n "$hits" ]; then
echo " FAIL: unsafe block without // SAFETY: in $f (CODING_GUIDELINES §10)" >&2
awk '{print " " $0}' <<< "$hits" >&2
fail=1
fi
done <<< "$engine_src"
[ "$fail" -eq 0 ] || exit 1
fi
fi
# --- Documentation consolidation guard (always) -----------------------------
bash scripts/check-docs.sh || exit 1
# --- Marketing site ---------------------------------------------------------
if [ -n "$site_staged" ] && [ -d site/node_modules ]; then
echo "pre-commit: eslint (site)"
( cd site && npx --no-install eslint . ) || exit 1
fi
exit 0