#!/usr/bin/env bash
# check.sh — the ROUND's verify (decision 1822): fmt everywhere (cheap), clippy + test scoped to
# the crates this round's changes can actually affect — the changed crates plus everything that
# depends on them. The workspace-wide chain is `scripts/gates.sh`'s job, paid at sync→land.
#
# Why this verb exists: the fleet was paying the FULL chain per round — measured over the fortnight
# to 2026-09-01 at 3,028 `cargo test --workspace` runs (72 h of wall time) and 1,572 workspace-wide
# clippys (33 h) against only 393 actual gate moments, one session hitting the whole suite 161
# times in a day while editing a single leaf crate. 59 % of edits land in `benilla-app`, which
# nothing but the `benilla` bin depends on: the honest scope of a round is 1–3 crates, not 19.
#
# Scope rule — conservative by construction, unknown means FULL, never "probably fine":
# crates/
/** → that crate (owner read off cargo metadata, not the dir name)
# assets/ui/** → benilla-app (ui_script's shipped-UI tests read it at runtime)
# *.md → no gate can change (gates.sh's docs-only rule)
# anything else → escalate: exec scripts/gates.sh (workspace manifests, .cargo/,
# rust-toolchain, scripts/, shaders outside crates, …)
# Change set = fork point vs main + staged + unstaged + untracked — the round's work, not the last
# edit. CHECK_FULL=1 skips straight to gates.sh.
#
# Same fail-fast discipline as gates.sh: never compose `cargo … | tail` pipelines by hand — the
# pipe's exit code masks the gate's (that bug bit twice before gates.sh existed).
set -uo pipefail
# Gate the tree you are STANDING IN (gates.sh learned this; same rule here).
here="$(cd "$(dirname "$0")/.." && pwd)"
root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$root" ] && [ -f "$root/scripts/check.sh" ] || root="$here"
cd "$root" || exit 1
if [ "${CHECK_FULL:-}" = "1" ]; then
exec "$root/scripts/gates.sh"
fi
# ── The round's change set ───────────────────────────────────────────────────────────────────────
base="$(git merge-base HEAD main 2>/dev/null || git rev-parse HEAD)"
changed="$( { git diff --name-only "$base" HEAD 2>/dev/null
git diff --name-only HEAD 2>/dev/null
git ls-files --others --exclude-standard 2>/dev/null; } | sort -u )"
if [ -z "$changed" ]; then
echo "check: no changes against main and a clean tree — nothing to verify"
exit 0
fi
# ── Map files → crate dirs; anything unmappable escalates to the full chain ──────────────────────
dirs=""
full_reason=""
while IFS= read -r f; do
case "$f" in
docs/* | *.md) ;; # provably gate-inert (gates.sh's docs-only rule)
assets/ui/*) dirs="$dirs benilla-app-DIR:crates/benilla-app" ;;
crates/*/*) d="${f#crates/}"; dirs="$dirs DIR:crates/${d%%/*}" ;;
*) full_reason="$f" ;;
esac
done <"$log" 2>&1; then
echo "CHECK FAILED: $name"
tail -30 "$log"
exit 1
fi
echo "check ok: $name"
}
run fmt cargo fmt --all -- --check
run clippy cargo clippy $pflags --all-targets -- -D warnings
run test env BENILLA_SKIP_LOG="$skips" cargo test $pflags
# The data-gated tests skip without the install or the addon corpus, and libtest swallows the
# line; the resolver logs each skip to $BENILLA_SKIP_LOG and this counts them (gates.sh says why).
if [ -s "$skips" ]; then
echo " test: $(wc -l <"$skips" | tr -d ' ') data-gated tests SKIPPED on this machine —"
sort "$skips" | uniq -c | sort -rn | sed 's/^ *\([0-9]*\) \(.*\)/ \1 × \2/'
echo " (they run where the data is: docs/CONTRIBUTING.md, \"Setting up\")"
fi
echo "CHECK GREEN (scoped: $pkgs)"
echo " (land still pays the full chain once: scripts/gates.sh — memoized, so an already-gated tree is free)"