|  | 
| 1 | 1 | #!/usr/bin/env bash | 
| 2 | 2 | set -euo pipefail | 
| 3 | 3 | 
 | 
| 4 |  | -# Enforce coupling between PRNG algorithm/version and golden regression vector. | 
|  | 4 | + | 
|  | 5 | +if [[ "${SKIP_HOOKS:-}" == 1 ]]; then | 
|  | 6 | +  exit 0 | 
|  | 7 | +fi | 
|  | 8 | + | 
|  | 9 | +# 1) PRNG coupling guard (existing logic) | 
| 5 | 10 | PRNG_FILE="crates/rmg-core/src/math/prng.rs" | 
| 6 | 11 | if git diff --cached --name-only | grep -qx "$PRNG_FILE"; then | 
| 7 | 12 |   DIFF=$(git diff --cached -- "$PRNG_FILE" || true) | 
| 8 |  | -  # Heuristics to detect algorithm changes: edits to these functions imply behavior change | 
| 9 | 13 |   if echo "$DIFF" | grep -E '^(\+|-)\s*(fn\s+next_u64|fn\s+from_seed_u64|fn\s+from_seed\(|fn\s+next_int\()' >/dev/null; then | 
| 10 | 14 |     ALGO_CHANGED=1 | 
| 11 | 15 |   else | 
| 12 | 16 |     ALGO_CHANGED=0 | 
| 13 | 17 |   fi | 
| 14 |  | - | 
| 15 |  | -  # Version bump present? | 
| 16 | 18 |   if echo "$DIFF" | grep -E 'PRNG_ALGO_VERSION' >/dev/null; then | 
| 17 | 19 |     VERSION_CHANGED=1 | 
| 18 | 20 |   else | 
| 19 | 21 |     VERSION_CHANGED=0 | 
| 20 | 22 |   fi | 
| 21 |  | - | 
| 22 |  | -  # Golden regression vector updated? | 
| 23 | 23 |   if echo "$DIFF" | grep -E 'next_int_golden_regression|assert_eq!\(values,\s*vec!\[' >/dev/null; then | 
| 24 | 24 |     GOLDEN_CHANGED=1 | 
| 25 | 25 |   else | 
| 26 | 26 |     GOLDEN_CHANGED=0 | 
| 27 | 27 |   fi | 
| 28 |  | - | 
| 29 | 28 |   FAIL=0 | 
| 30 | 29 |   if [[ "$ALGO_CHANGED" -eq 1 && "$VERSION_CHANGED" -eq 0 ]]; then | 
| 31 | 30 |     echo "pre-commit: PRNG algorithm changed but PRNG_ALGO_VERSION was not bumped." >&2 | 
| 32 | 31 |     FAIL=1 | 
| 33 | 32 |   fi | 
| 34 |  | - | 
| 35 | 33 |   if [[ "$VERSION_CHANGED" -eq 1 && "$GOLDEN_CHANGED" -eq 0 ]]; then | 
| 36 | 34 |     echo "pre-commit: PRNG_ALGO_VERSION bumped but golden regression vector was not updated." >&2 | 
| 37 | 35 |     FAIL=1 | 
| 38 | 36 |   fi | 
| 39 |  | - | 
| 40 | 37 |   if [[ "$FAIL" -eq 1 ]]; then | 
| 41 | 38 |     echo "pre-commit: Refusing commit. Update algorithm version and golden regression together." >&2 | 
| 42 | 39 |     exit 1 | 
| 43 | 40 |   fi | 
| 44 | 41 | fi | 
| 45 | 42 | 
 | 
| 46 |  | -# Format gate: run rustfmt check if Rust files are staged | 
| 47 |  | -if git diff --cached --name-only | grep -E '\.rs$' >/dev/null; then | 
| 48 |  | -  echo "[pre-commit] Running cargo fmt --all -- --check ..." | 
| 49 |  | -  if ! cargo fmt --all -- --check; then | 
| 50 |  | -    cat <<'EOM' >&2 | 
| 51 |  | -[pre-commit] Formatting check failed. | 
|  | 43 | +# 2) Enforce toolchain pin (matches rust-toolchain.toml) | 
|  | 44 | +if command -v rustup >/dev/null 2>&1; then | 
|  | 45 | +  PINNED=$(awk -F '"' '/^channel/ {print $2}' rust-toolchain.toml 2>/dev/null || echo "") | 
|  | 46 | +  ACTIVE=$(rustup show active-toolchain 2>/dev/null | awk '{print $1}') | 
|  | 47 | +  if [[ -n "$PINNED" && "$ACTIVE" != "$PINNED"* ]]; then | 
|  | 48 | +    echo "pre-commit: Active toolchain '$ACTIVE' != pinned '$PINNED'. Run: rustup override set $PINNED" >&2 | 
|  | 49 | +    exit 1 | 
|  | 50 | +  fi | 
|  | 51 | +fi | 
| 52 | 52 | 
 | 
| 53 |  | -Run:  cargo fmt --all | 
|  | 53 | +# 3) Format (auto-fix if opted in) | 
|  | 54 | +_auto_fmt="${ECHO_AUTO_FMT:-1}" | 
|  | 55 | +case "${_auto_fmt}" in | 
|  | 56 | +  1|true|TRUE|yes|YES|on|ON) | 
|  | 57 | +    echo "pre-commit: ECHO_AUTO_FMT=${_auto_fmt} → running cargo fmt (auto-fix)" | 
|  | 58 | +    cargo fmt --all || { echo "pre-commit: cargo fmt failed" >&2; exit 1; } | 
|  | 59 | +    # Re-stage only staged files that were reformatted | 
|  | 60 | +    if STAGED=$(git diff --cached --name-only); then | 
|  | 61 | +      if [[ -n "$STAGED" ]]; then | 
|  | 62 | +        echo "$STAGED" | xargs -r git add -- | 
|  | 63 | +      fi | 
|  | 64 | +    fi | 
|  | 65 | +    ;; | 
|  | 66 | +  0|false|FALSE|no|NO|off|OFF) | 
|  | 67 | +    cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; } | 
|  | 68 | +    ;; | 
|  | 69 | +  *) | 
|  | 70 | +    # Unknown value → safest is check-only | 
|  | 71 | +    cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; } | 
|  | 72 | +    ;; | 
|  | 73 | +esac | 
| 54 | 74 | 
 | 
| 55 |  | -Our policy is to keep the tree rustfmt-clean to reduce review noise and | 
| 56 |  | -merge conflicts. The CI also enforces this gate. | 
| 57 |  | -EOM | 
|  | 75 | +# 4) Docs guard (scaled): only require docs when core public API changed | 
|  | 76 | +STAGED=$(git diff --cached --name-only) | 
|  | 77 | +CORE_API_CHANGED=$(echo "$STAGED" | grep -E '^crates/rmg-core/src/.*\.rs$' | grep -v '/tests/' || true) | 
|  | 78 | +if [[ -n "$CORE_API_CHANGED" ]]; then | 
|  | 79 | +  echo "$STAGED" | grep -Fx 'docs/execution-plan.md' >/dev/null || { echo 'pre-commit: docs/execution-plan.md must be updated when core API changes.' >&2; exit 1; } | 
|  | 80 | +  echo "$STAGED" | grep -Fx 'docs/decision-log.md' >/dev/null || { echo 'pre-commit: docs/decision-log.md must be updated when core API changes.' >&2; exit 1; } | 
|  | 81 | +fi | 
|  | 82 | + | 
|  | 83 | +# 5) Lockfile guard: ensure lockfile version is v3 (current cargo format) | 
|  | 84 | +if [[ -f Cargo.lock ]]; then | 
|  | 85 | +  # Normalize detected lockfile version (strip quotes/CR/whitespace) | 
|  | 86 | +  VER_LINE=$(grep -n '^version = ' Cargo.lock | head -n1 | awk -F'= ' '{print $2}' | tr -d '\r' | tr -d '"' | xargs) | 
|  | 87 | +  if [[ "$VER_LINE" != "3" ]]; then | 
|  | 88 | +    # Determine pinned toolchain (normalize), fallback to rust-toolchain.toml if unset | 
|  | 89 | +    _PINNED_RAW="${PINNED:-}" | 
|  | 90 | +    if [[ -z "$_PINNED_RAW" ]]; then | 
|  | 91 | +      _PINNED_RAW=$(awk -F '"' '/^channel/ {print $2}' rust-toolchain.toml 2>/dev/null || echo "") | 
|  | 92 | +    fi | 
|  | 93 | +    PINNED_NORM=$(printf "%s" "$_PINNED_RAW" | tr -d '\r' | xargs) | 
|  | 94 | +    echo "pre-commit: Cargo.lock must be lockfile format v3 (found '$VER_LINE')." >&2 | 
|  | 95 | +    echo "Run: cargo +${PINNED_NORM} generate-lockfile" >&2 | 
| 58 | 96 |     exit 1 | 
| 59 | 97 |   fi | 
| 60 | 98 | fi | 
|  | 99 | + | 
|  | 100 | +# 6) Targeted clippy + check for changed crates (fast-ish) | 
|  | 101 | +CRATES=$(echo "$STAGED" | sed -n 's#^crates/\([^/]*\)/.*#\1#p' | sort -u) | 
|  | 102 | +for c in $CRATES; do | 
|  | 103 | +  cargo clippy -p "$c" --all-targets -- -D warnings -D missing_docs | 
|  | 104 | +  cargo check -p "$c" --quiet | 
|  | 105 | +done | 
|  | 106 | + | 
|  | 107 | +exit 0 | 
0 commit comments