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.
115 lines
4.9 KiB
Bash
Executable File
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
|