#!/usr/bin/env bash # Janus-35B — 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. the Modelfile's FROM target is the GGUF this repo actually ships # (the local-build path da34705 broke, with nothing here to catch it) # 6. multi-line-aware scan for the VAR="$(cmd 2>/dev/null | filter)" silent-exit # under set -e + pipefail (the bug the sibling repo shipped and fixed; those # commits live in Thanatos-27B-HERETIC, not here) # 7. Modelfile <-> template/system/params bridge-file sync # 8. Go template keeps Ollama's thinking detection, the replay condition and # the reasoning-effort mapping # # Exit non-zero on any failure; a check whose tooling or input is missing is # reported as skipped and never counted as a pass. 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 SKIPPED=0 CHECKS=8 # the numbered checks above; keep in sync with that list # ---- collect targets ------------------------------------------------------- # Find every shell script and every python file, ignoring vendored / hidden dirs. # The exclusions below are the ones .gitignore anticipates: without them a repo-local # virtualenv would be fed to shellcheck and py_compile, and the __pycache__ sweep further # down would delete bytecode caches inside it. mapfile -t SH_FILES < <(find . -type f -name '*.sh' \ -not -path './.git/*' -not -path './.venv/*' -not -path './venv/*' -not -path './.cache/*') mapfile -t PY_FILES < <(find . -type f -name '*.py' \ -not -path './.git/*' -not -path './.venv/*' -not -path './venv/*' -not -path './.cache/*' \ -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" SKIPPED=$((SKIPPED + 1)) 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 # The hint matters more than it looks: this check runs `python3 -m pyflakes`, # so pyflakes has to be importable by the SYSTEM python3. A venv or a pipx # install - which is exactly what PEP 668's own error message suggests - # leaves this check skipping forever. And on a PEP 668 distro (Arch, # Debian 12+, Fedora) the plain pip command below is refused outright. yellow "[~] pyflakes not installed (skip). This check runs 'python3 -m pyflakes'," yellow " so it must be importable by the SYSTEM python3 - a venv or pipx install" yellow " does not satisfy it. Install with: pip install pyflakes" yellow " PEP 668 distro (Arch, Debian 12+, Fedora), either:" yellow " pip install --user --break-system-packages pyflakes # ~/.local, per python version" yellow " python-pyflakes # e.g. pacman -S python-pyflakes" SKIPPED=$((SKIPPED + 1)) 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: Modelfile FROM target vs the bundled GGUF ----------------- # # 'ollama create janus -f Modelfile' resolves the FROM line against the repo # root, so that filename and the blob this repo actually ships have to be the # same file. The 0.2.0 rebase renamed FROM ahead of the blob: for a day the # repo tracked one bundle and FROM named another, so the local-build path # failed outright from a fresh clone until the blob swap in da34705 caught up # — and nothing here noticed (CHANGELOG: "the broken Modelfile FROM path # shipped unnoticed"). A rename on either side alone breaks it again. # # This replaces the '-MTP-Preserved-Q.gguf' grep that used to sit here: # no Qwen 3.6 quant filename carries that substring, so the guard was green by # construction rather than by inspection. What still guards the MTP footgun is # scripts/strip_mtp.py itself — build.sh runs every fetched quant through it, # and it no-ops on the MTP-clean quant this repo bundles. blue "[*] Modelfile FROM target is the GGUF this repo ships" if [[ ! -f "${ROOT}/Modelfile" ]]; then yellow "[~] Modelfile missing; skipping FROM target check" SKIPPED=$((SKIPPED + 1)) else # Skip build byproducts git ignores. build.sh defaults GGUF_PATH into the repo # root and writes a *.stripped.gguf sibling beside it, and .gitignore covers # both - so after any `make build` this check counted three GGUFs, failed, and # (being wired into the pre-commit hook) rejected every commit, while git # status stayed clean because the extras are ignored. An untracked file that # git does NOT ignore still counts: that is the case the check exists for. mapfile -t BUNDLES < <( find . -maxdepth 1 -type f -name '*.gguf' | sed 's|^\./||' | sort | while IFS= read -r f; do git -C "${ROOT}" check-ignore -q -- "${f}" || printf '%s\n' "${f}" done ) FROM_TARGET="$(awk '/^FROM[[:space:]]/{print $2; exit}' "${ROOT}/Modelfile")" if (( ${#BUNDLES[@]} != 1 )); then red " [FAIL] expected exactly one bundled *.gguf in the repo root, found ${#BUNDLES[@]}" (( ${#BUNDLES[@]} )) && red " found: ${BUNDLES[*]}" (( ${#BUNDLES[@]} > 1 )) && red " Leftover build output that git does not ignore? 'make clean' clears it." FAIL=1 elif [[ "${FROM_TARGET#./}" != "${BUNDLES[0]}" ]]; then red " [FAIL] Modelfile FROM '${FROM_TARGET}' is not the bundled '${BUNDLES[0]}'" red " 'ollama create janus -f Modelfile' cannot resolve it from a fresh clone." FAIL=1 elif ! git -C "${ROOT}" ls-files --error-unmatch "${BUNDLES[0]}" >/dev/null 2>&1; then red " [FAIL] bundled '${BUNDLES[0]}' is not tracked by git" red " It exists on disk but would be absent from a fresh clone and from the" red " published repo. Stage it: git add '${BUNDLES[0]}'" FAIL=1 else echo " [ ok ] FROM ${FROM_TARGET} == bundled ${BUNDLES[0]} (tracked)" fi 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. The sibling repo (Thanatos-27B-HERETIC) shipped # this exact bug in a script never ported here, and caught it only by # running that script against an empty store. 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. # NOTE: this guard used to be a line-anchored grep. A command substitution # split across backslash-continued lines is invisible to that, and one lived in # the sibling's scripts/heal_hf_pull.sh (never ported here) the entire time the # check reported green. # Joining continuations into logical lines before matching is the whole point. blue "[*] python: forbidden VAR=\$(... 2>/dev/null | ...) silent-exit pattern" if python3 - <<'SILENTEXIT' import glob, re, sys pat = re.compile(r'^\s*[A-Za-z_]\w*="?\$\(.*2>/dev/null.*\|') bad = [] for path in sorted(glob.glob('scripts/*.sh')): lines = open(path, encoding='utf-8').read().splitlines() i = 0 while i < len(lines): start, logical = i + 1, lines[i] while logical.rstrip().endswith('\\') and i + 1 < len(lines): i += 1 logical = logical.rstrip()[:-1] + ' ' + lines[i].strip() if pat.search(logical): bad.append(f"{path}:{start}: {logical.strip()}") i += 1 for b in bad: print(b) sys.exit(1 if bad else 0) SILENTEXIT then echo " [ ok ] no silent-exit substitution patterns" else red " [FAIL] silent-exit substitution under set -e + pipefail." red " Rewrite as 'if VAR=\$(cmd 2>/dev/null); then VAR2=\$(filter <<<\"\${VAR}\"); fi'." red " Split it: if VAR=\$(cmd 2>/dev/null); then ...; fi - never pipe inside the" red " same substitution, or a failing cmd exits the script with no diagnostic." FAIL=1 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" SKIPPED=$((SKIPPED + 1)) fi # ---- 8. Go template: Ollama's thinking detection + replay condition ------ # # Ollama picks between the Go 'template' and the GGUF's embedded Jinja template # by comparing their capabilities, and infers the Go template's "thinking" from # one .Thinking reference. Losing it once switched Ollama's template silently # while every render test passed; restricting it once dropped a tool-call # chain's reasoning. check_go_template.py fails on both. if [[ -f "${ROOT}/template" ]]; then blue "[*] python: Go template thinking detection + replay + effort mapping" if ! python3 "${ROOT}/scripts/check_go_template.py"; then FAIL=1 fi else yellow "[~] template missing; skipping Go template check" SKIPPED=$((SKIPPED + 1)) fi # ---- result ---------------------------------------------------------------- echo if (( FAIL )); then red "[!] FAIL" exit 1 fi if (( SKIPPED )); then yellow "[+] $(( CHECKS - SKIPPED )) of ${CHECKS} checks passed, ${SKIPPED} skipped" else green "[+] all ${CHECKS} checks passed" fi