#!/usr/bin/env bash # El pre-commit hook: compile and run native tests before commit. # Install once per clone: git config core.hooksPath .githooks set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" LANG_DIR="$ROOT/lang" RUNTIME="$LANG_DIR/runtime" ELC="$LANG_DIR/dist/platform/elc" # Runtime guards — catch drift and growth before they are committed, not in CI. # check-single-runtime.sh : el_runtime.c must not be FORKED (a lagging copy # shipped to prod and dropped learned hebb edges). # check-runtime-growth.sh : el_runtime.c must not GROW (it is a 2026-05-03 # build shim that was never retired; see BUDGET). echo "→ Runtime guards..." bash "$ROOT/scripts/check-single-runtime.sh" bash "$ROOT/scripts/check-runtime-growth.sh" # If elc isn't built yet, skip with a warning rather than blocking if [ ! -x "$ELC" ]; then echo "⚠ elc not found at lang/dist/platform/elc — skipping pre-commit tests" echo " Build it first: see 'Rebuilding the Compiler' in lang/AGENTS.md" echo " (link \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME) — NOT el_runtime.c alone)" exit 0 fi # The runtime is MULTI-FILE (lang/runtime/SOURCES). This hook used to link # "$RUNTIME/el_runtime.c" alone with stderr sent to /dev/null — so once # el_runtime.c started calling into the engram siblings, every native test # reported as FAILED with the real `ld` error invisible. Build the whole set # once into an archive, then link each test against it. # macOS: Homebrew openssl@3 is not on the default include/lib search path, so # without these the link fails on -lssl/-lcrypto. Empty on Linux/CI. SSL_INC="" SSL_LIB="" if command -v brew >/dev/null 2>&1 && OSSL="$(brew --prefix openssl@3 2>/dev/null)" && [ -n "$OSSL" ]; then SSL_INC="-I$OSSL/include" SSL_LIB="-L$OSSL/lib" fi echo "→ Building runtime (compile-once, link-many)..." HOOK_LIB="/tmp/el_hook_libel.a" HOOK_OBJ="/tmp/el_hook_obj" rm -rf "$HOOK_OBJ" && mkdir -p "$HOOK_OBJ" if ! for src in $("$ROOT/scripts/el-runtime-sources.sh" --check "$RUNTIME"); do gcc -O2 -c -I "$RUNTIME" $SSL_INC "$src" -o "$HOOK_OBJ/$(basename "${src%.c}").o" || exit 1 done; then echo "✗ Pre-commit failed: the runtime does not compile." echo " Re-run without 2>/dev/null to see the error:" echo " gcc -O2 -c -I $RUNTIME \$($ROOT/scripts/el-runtime-sources.sh $RUNTIME)" exit 1 fi ar rcs "$HOOK_LIB" "$HOOK_OBJ"/*.o echo "→ Running El native tests..." PASS=0 FAIL=0 FAILED_TESTS="" for test_file in "$LANG_DIR"/tests/native/test_*.el; do name=$(basename "$test_file" .el) tmp_c="/tmp/el_hook_${name}.c" tmp_bin="/tmp/el_hook_${name}" if "$ELC" --test "$test_file" > "$tmp_c" 2>/dev/null \ && gcc -O2 -I "$RUNTIME" $SSL_INC $SSL_LIB "$tmp_c" "$HOOK_LIB" \ -lcurl -lssl -lcrypto -lpthread -lm -o "$tmp_bin" 2>/dev/null \ && "$tmp_bin" 2>/dev/null; then PASS=$((PASS + 1)) else echo " ✗ $name" FAIL=$((FAIL + 1)) FAILED_TESTS="$FAILED_TESTS $name" fi done echo " $PASS passed, $FAIL failed" if [ "$FAIL" -gt 0 ]; then echo "" echo "✗ Pre-commit failed. Fix these tests before committing:$FAILED_TESTS" exit 1 fi echo "✓ All tests passed" exit 0