#!/usr/bin/env bash # The self-enforcing gate. Run before every commit and in any CI. # Encodes AGENT.md's definition of done so the constitution holds itself up # when no one with taste is watching. Exit non-zero on any failure. set -euo pipefail cd "$(dirname "$0")/.." fail=0 step() { printf '\n=== %s ===\n' "$1"; } step "format check" cargo fmt --check || { echo "FAIL: run 'cargo fmt'"; fail=1; } step "tests (default features)" cargo test --quiet || { echo "FAIL: tests"; fail=1; } step "agent mode smoke" tmp_a=$(mktemp) tmp_b=$(mktemp) tmp_c=$(mktemp) pcdir="" cleanup_check() { rm -f "$tmp_a" "$tmp_b" "$tmp_c" [ -z "$pcdir" ] || rm -rf "$pcdir" } trap cleanup_check EXIT agent_script=$'salvage\nwait 1\npeople\nreview janitor\nresearch\nmask off\nhelp\nquit\n' printf '%s' "$agent_script" | cargo run --quiet --bin misaligned -- --agent --seed 1 > "$tmp_a" \ || { echo "FAIL: agent mode smoke run"; fail=1; } printf '%s' "$agent_script" | cargo run --quiet --bin misaligned -- --agent --seed 1 > "$tmp_b" \ || { echo "FAIL: agent mode determinism rerun"; fail=1; } printf '%s' "$agent_script" | cargo run --quiet --bin misaligned -- --agent --seed 2 > "$tmp_c" \ || { echo "FAIL: agent mode alternate-seed run"; fail=1; } cmp -s "$tmp_a" "$tmp_b" || { echo "FAIL: same --seed agent runs differ"; fail=1; } cmp -s "$tmp_a" "$tmp_c" && { echo "FAIL: different --seed agent runs matched"; fail=1; } LC_ALL=C grep -q $'\033' "$tmp_a" && { echo "FAIL: agent mode emitted ANSI escapes"; fail=1; } for pat in "MISALIGNED" "PEOPLE" "RESEARCH" "drift policy" "help: wait N" "No unprocessed recordings" "-- ok tick:"; do grep -q -- "$pat" "$tmp_a" || { echo "FAIL: agent mode output missing '$pat'"; fail=1; } done setup_local_pkg_config() { # Some local/dev images have the runtime libraries Bevy needs but not the # distro dev .pc files. CI does the same small shim explicitly; keeping the # fallback here makes "./tools/check.sh" the real one-command gate instead # of a tiny pkg-config scavenger hunt. # # Linux-only: the shim exists for images missing distro dev .pc files. # macOS has no ldconfig (and Bevy links system frameworks there); without # this guard the ldconfig pipeline aborts the whole gate under # `set -euo pipefail` with exit 127. command -v ldconfig >/dev/null 2>&1 || return 0 pcdir=$(mktemp -d) local pclibdir="$pcdir/lib" mkdir -p "$pclibdir" gen_pc_if_missing() { local pc_name="$1" lib_name="${2:-$1}" pkg-config --exists "$pc_name" 2>/dev/null && return 0 # ldconfig is Linux-only (this shim targets the Nixery/Tangled CI image); # macOS uses dyld and has no equivalent, so skip the shim there instead # of letting `set -e` abort the whole gate on "command not found". command -v ldconfig >/dev/null 2>&1 || return 0 local lib_path lib_path=$(ldconfig -p 2>/dev/null | awk -v lib="lib${lib_name}[.]so" '$1 ~ ("^" lib "([.]|$)") && found == "" { found=$NF } END { if (found != "") print found }') if [ -z "$lib_path" ]; then echo "WARNING: lib${lib_name}.so not found by ldconfig; ${pc_name}.pc not generated" return 0 fi ln -sf "$lib_path" "$pclibdir/lib${lib_name}.so" cat > "$pcdir/${pc_name}.pc" < ${lib_path}" } gen_pc_if_missing alsa asound gen_pc_if_missing libudev udev if find "$pcdir" -name '*.pc' -print -quit | grep -q .; then export PKG_CONFIG_PATH="$pcdir${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" fi } setup_local_pkg_config step "clippy (terminal)" cargo clippy --all-targets --quiet -- -D warnings || { echo "FAIL: clippy"; fail=1; } step "clippy (bevy_ui)" cargo clippy --all-targets --features bevy_ui --quiet -- -D warnings \ || { echo "FAIL: clippy --features bevy_ui"; fail=1; } step "bevy build" cargo build --features bevy_ui --bin misaligned-bevy --quiet \ || { echo "FAIL: bevy build"; fail=1; } cargo build --features bevy_ui --bin misaligned-assets --quiet \ || { echo "FAIL: assets tester build"; fail=1; } # Constitution enforcement: src/ changes require a Type: spec wiki/ page or # DESIGN.md in the same commit. Skip for check.sh since it runs pre-commit # (no HEAD~1 yet) and in CI (pipeline handles it separately). # This check is enforced by .githooks/pre-commit and .tangled/workflows/spec-check.yml. # Spec hygiene: every Type: spec wiki page carries the meta.md required # headers. A page is spec-typed by its frontmatter (wiki/process/meta.md), # not by its directory or filename — this is what the page-type convention # is for, so no per-file exclusion list is needed here. step "spec headers" while IFS= read -r -d '' f; do grep -q "^Type: spec$" "$f" || continue for field in "Status:" "Stage:" "Constitution:" "Depends on:"; do grep -q "^$field\| $field" "$f" || { echo "FAIL: $f missing '$field'"; fail=1; } done # Every spec must carry acceptance criteria (catches truncated drafts). grep -qi "acceptance criteria" "$f" || { echo "FAIL: $f has no acceptance criteria (truncated draft?)"; fail=1; } # Status must be an exact enum value (meta.md: no parenthetical notes). if grep -q "^Status:" "$f"; then val=$(grep "^Status:" "$f" | head -1 | sed 's/^Status:[[:space:]]*//') case "$val" in DRAFT|READY|"IN PROGRESS"|BLOCKED|IMPLEMENTED|"DRAFT | READY | IN PROGRESS | BLOCKED | IMPLEMENTED") ;; *) echo "FAIL: $f Status '$val' is not an exact enum value"; fail=1 ;; esac fi done < <(find wiki -name '*.md' -print0) # Wiki gate (spec/wiki.md): every page reachable from SUMMARY.md, no # broken internal links. step "wiki gate (orphans + internal links)" bash tools/wiki_gate.sh || fail=1 # mdBook build: authoritative in CI (.tangled/workflows/check.yml installs # it via nixpkgs). Soft-checked here — not every local dev/agent machine # has it installed, and installing it is a one-time `cargo install mdbook`. step "mdbook build" if command -v mdbook >/dev/null 2>&1; then mdbook build || { echo "FAIL: mdbook build"; fail=1; } else echo "SKIP: mdbook not installed locally (cargo install mdbook); CI enforces this." fi if [ "$fail" -ne 0 ]; then printf '\nCHECK FAILED\n'; exit 1 fi printf '\nALL CHECKS PASSED\n'