#!/usr/bin/env bash # Thanatos-27B — repo-local sanity checks. # # Runs everything that's cheap and catches a real-world bug we've already hit: # # 1. bash -n on every *.sh (catches syntax errors) # 2. shellcheck on every *.sh, if installed (catches quoting/SC2086 bugs) # 3. python3 -m pyflakes on every *.py (catches NameError, unused imports) # 4. python3 -m py_compile on every *.py (catches actual syntax errors) # 5. grep -E for the dash/dot filename footgun (the one we shipped in 6f2884f # and had to fix in 82677d0) # 6. grep -E for the VAR="$(cmd 2>/dev/null | filter)" silent-exit pattern # under set -e + pipefail (the one we shipped in d87bc64 and fixed in 385ed94) # # Exit non-zero on any failure. Designed to run from .git/hooks/pre-commit. # # Usage: # ./scripts/check.sh # one-shot # ./scripts/install-hooks.sh # install as pre-commit hook set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${ROOT}" red() { printf "\033[31m%s\033[0m\n" "$*"; } green() { printf "\033[32m%s\033[0m\n" "$*"; } yellow() { printf "\033[33m%s\033[0m\n" "$*"; } blue() { printf "\033[34m%s\033[0m\n" "$*"; } FAIL=0 # ---- collect targets ------------------------------------------------------- # Find every shell script and every python file, ignoring vendored / hidden dirs. mapfile -t SH_FILES < <(find . -type f -name '*.sh' -not -path './.git/*') mapfile -t PY_FILES < <(find . -type f -name '*.py' -not -path './.git/*' -not -path '*/__pycache__/*') blue "[*] checking ${#SH_FILES[@]} shell file(s) and ${#PY_FILES[@]} python file(s)" # ---- 1. bash -n ------------------------------------------------------------ if (( ${#SH_FILES[@]} )); then blue "[*] bash -n" for f in "${SH_FILES[@]}"; do if ! bash -n "$f" 2>/dev/null; then red " [FAIL] bash -n $f" bash -n "$f" || true FAIL=1 else echo " [ ok ] $f" fi done fi # ---- 2. shellcheck (optional) --------------------------------------------- if command -v shellcheck >/dev/null 2>&1; then blue "[*] shellcheck" for f in "${SH_FILES[@]}"; do if ! shellcheck -S warning "$f"; then red " [FAIL] shellcheck $f" FAIL=1 else echo " [ ok ] $f" fi done else yellow "[~] shellcheck not installed (skip). Install with: apt install shellcheck" fi # ---- 3. pyflakes ----------------------------------------------------------- if (( ${#PY_FILES[@]} )); then if python3 -c 'import pyflakes' 2>/dev/null; then blue "[*] pyflakes" for f in "${PY_FILES[@]}"; do if ! python3 -m pyflakes "$f"; then red " [FAIL] pyflakes $f" FAIL=1 else echo " [ ok ] $f" fi done else yellow "[~] pyflakes not installed (skip). Install with: pip install pyflakes" fi fi # ---- 4. py_compile --------------------------------------------------------- if (( ${#PY_FILES[@]} )); then blue "[*] python3 -m py_compile" for f in "${PY_FILES[@]}"; do if ! python3 -m py_compile "$f" 2>&1; then red " [FAIL] py_compile $f" FAIL=1 else echo " [ ok ] $f" fi done # py_compile leaves __pycache__ artifacts; clean them up. find . -type d -name __pycache__ -not -path './.git/*' -exec rm -rf {} + 2>/dev/null || true fi # ---- 5. footgun: dot-vs-dash filename ------------------------------------- # # Upstream unsloth/Qwen3.6-27B-GGUF uses dashes (Qwen3.6-27B-Q4_K_M.gguf). # Earlier commits used the wrong dot-separated pattern, which 404s. # Block re-introduction. blue "[*] grep: forbidden Qwen3.6-27B.Q* filename pattern" if grep -RnE 'Qwen3\.6-27B\.Q[0-9A-Z_]+\.gguf' \ --include='*.sh' --include='*.py' --include='*.md' \ --include='Modelfile*' --include='*.cff' \ --exclude-dir=.git --exclude-dir=__pycache__ \ . ; then red " [FAIL] found banned dot pattern (use 'Qwen3.6-27B-Q*.gguf')" FAIL=1 else echo " [ ok ] no stale dot-pattern matches" fi # ---- 6. footgun: VAR="$(cmd 2>/dev/null | filter)" silent-exit pattern ----- # # Under `set -euo pipefail`, a direct command substitution like # VAR="$(ollama show "${TAG}" 2>/dev/null | awk ...)" # silently kills the script when ollama show fails: pipefail # propagates the non-zero exit through the pipeline, set -e # aborts on the assignment, and the explicit `[[ -z "${VAR}" ]]` # check below it never runs. The user sees only # make: *** [Makefile:N: ] Error 1 # with no diagnostic. We shipped this exact bug in # scripts/heal_hf_pull.sh and only caught it this session by # running `make heal-hf` against an empty store — see commit # 385ed94 for the fix recipe: split the assignment with # if VAR="$(cmd 2>/dev/null)"; then # VAR2="$(filter <<<"${VAR}")" # fi # The `2>/dev/null` is the tell — its presence says "this command # can fail loudly, we want to suppress the noise" — which is # exactly the case where set -e + pipefail silently kills. blue "[*] grep: forbidden VAR=\$(... 2>/dev/null | ...) silent-exit pattern" if grep -RnE '^\s*[A-Za-z_][A-Za-z0-9_]*=("?)\$\([^)]*2>/dev/null[^|]*\|' \ --include='*.sh' \ --exclude-dir=.git \ scripts/ ; then red " [FAIL] silent-exit substitution under set -e + pipefail." red " Rewrite as 'if VAR=\$(cmd 2>/dev/null); then VAR2=\$(filter <<<\"\${VAR}\"); fi'." red " See commit 385ed94 for the bug we shipped and the fix recipe." FAIL=1 else echo " [ ok ] no silent-exit substitution patterns" fi # ---- 7. Modelfile <-> bridge files sync ----------------------------------- # # 'Modelfile' (consumed by 'ollama create -f Modelfile') and the root-level # 'template' / 'system' / 'params' files (consumed by HF's Ollama bridge, # which does NOT read Modelfile) must stay in sync. If they drift, hf.co/... # users and 'make build' users get different behaviour. if [[ -f "${ROOT}/Modelfile" && -f "${ROOT}/template" \ && -f "${ROOT}/system" && -f "${ROOT}/params" ]]; then blue "[*] python: Modelfile <-> bridge files sync" if ! python3 "${ROOT}/scripts/check_bridge_sync.py"; then FAIL=1 fi else yellow "[~] bridge files missing; skipping sync check" fi # ---- result ---------------------------------------------------------------- echo if (( FAIL )); then red "[!] FAIL" exit 1 fi green "[+] all checks passed"