#!/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