Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development. forked from cameron.stream/misaligned
Something went wrong. Try again.
18 kB · 484 lines
Shell
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485#!/usr/bin/env bash# The self-enforcing gate. Classifies the task delta and runs a proportional# gate: docs-only, lib-only, frontend-only, or full Rust/Bevy.## Usage:# ./tools/check.sh # auto-classify from changed files# ./tools/check.sh --docs # corpus + wiki + env only# ./tools/check.sh --lib # fmt, lib tests, terminal clippy, cheap bevy check# ./tools/check.sh --frontend # fmt, bevy bin tests, bevy clippy# ./tools/check.sh --full # everything (also MISALIGNED_FORCE_RUST_GATE=1)# ./tools/check.sh --land # land policy: auto-classify (full when unclear)## Concurrent agents: at most one Rust gate runs at a time on this machine# (mkdir lock). Docs gates never take the lock. `tools/claim.sh` records# advisory task surfaces; it is not a semantic mutex.## An unclassifiable clean primary checkout stays conservative (full). Tangled# runs the same docs gates separately and uses the knot's complete ref-update# file list through the workflow's native push path filter.# Exit non-zero on any failure.set -euo pipefailcd "$(dirname "$0")/.."
fail=0step() { printf '\n=== %s ===\n' "$1"; }
# ── Mode selection ───────────────────────────────────────────────────────mode=autofor arg in "$@"; do case "$arg" in --docs|--lib|--frontend|--full|--land) mode=${arg#--} ;; -h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "unknown argument: $arg (try --docs|--lib|--frontend|--full|--land)" exit 2 ;; esacdone
if [ "${MISALIGNED_FORCE_RUST_GATE:-0}" = "1" ]; then mode=fullfi
# --land is the named land-phase gate: same as auto (full when unclassifiable).if [ "$mode" = "land" ]; then mode=auto printf 'check phase: land (auto-classify)\n'fi
changed_files() { # Classify both uncommitted work and commits on a linked task worktree. # CI and the primary checkout normally have a .git directory (not the # .git file used by linked worktrees), so they retain the full gate. { git diff --name-only HEAD -- git ls-files --others --exclude-standard
if [ -f .git ] && git rev-parse --verify origin/main >/dev/null 2>&1; then local base base=$(git merge-base HEAD origin/main) git diff --name-only "$base"...HEAD -- fi } | sort -u}
# Returns: docs | lib | frontend | fullclassify_auto() { local changed path changed=$(changed_files)
if [ -z "$changed" ]; then # CI, primary checkout, or a task branch with no delta: run everything. printf 'full\n' return fi
local has_rust=0 has_lib=0 has_frontend=0 has_cargo_meta=0
while IFS= read -r path; do [ -z "$path" ] && continue case "$path" in Cargo.toml|Cargo.lock|build.rs|rust-toolchain*|.cargo/*|tests/*|benches/*|examples/*|crates/*/Cargo.toml) has_rust=1 has_cargo_meta=1 ;; crates/misaligned-bevy/*|crates/misaligned-assets/*) has_rust=1 has_frontend=1 ;; crates/misaligned-core/*|crates/misaligned-terminal/*) has_rust=1 has_lib=1 ;; assets/plots/*) # Plot TOML is compiled executable content. The generated catalog and # semantic validator only run when the core crate is built. has_rust=1 has_lib=1 ;; # Legacy monorepo paths (should not appear after workspace land) src/bin/bevy.rs|src/bin/assets|src/bin/assets/*) has_rust=1 has_frontend=1 ;; src/*) has_rust=1 has_lib=1 ;; esac done <<< "$changed"
if [ "$has_rust" -eq 0 ]; then printf 'docs\n' elif [ "$has_cargo_meta" -eq 1 ]; then printf 'full\n' elif [ "$has_frontend" -eq 1 ] && [ "$has_lib" -eq 0 ]; then printf 'frontend\n' elif [ "$has_lib" -eq 1 ] && [ "$has_frontend" -eq 0 ]; then printf 'lib\n' else printf 'full\n' fi}
if [ "$mode" = "auto" ]; then mode=$(classify_auto)fi
rust_gate=0case "$mode" in docs) rust_gate=0 ;; lib|frontend|full) rust_gate=1 ;; *) echo "internal error: unknown mode $mode"; exit 2 ;;esac
printf 'check mode: %s\n' "$mode"
guard_cargo_target_dir() { # Sharing one CARGO_TARGET_DIR across git worktrees can produce false-green # checks: Cargo may reuse the local package artifact from another checkout. # Seed private targets with tools/seed-cargo-target.sh instead. [ -n "${CARGO_TARGET_DIR:-}" ] || return 0 [ "${MISALIGNED_ALLOW_EXTERNAL_TARGET:-0}" = "1" ] && return 0
local root target default_target root=$(pwd -P) mkdir -p "$CARGO_TARGET_DIR" "$root/target" target=$(cd "$CARGO_TARGET_DIR" && pwd -P) default_target=$(cd "$root/target" && pwd -P)
if [ "$target" != "$default_target" ]; then echo "FAIL: CARGO_TARGET_DIR points outside this worktree: $target" echo " Cargo target dirs are not safe to share across Misaligned worktrees." echo " Run tools/seed-cargo-target.sh once, then unset CARGO_TARGET_DIR." echo " Override only for a deliberate isolated target with MISALIGNED_ALLOW_EXTERNAL_TARGET=1." exit 1 fi}
# ── Always-on cheap steps ────────────────────────────────────────────────step "script syntax"for script in tools/check.sh tools/corpus_gate.sh tools/wiki_gate.sh tools/design_amendment_gate.sh tools/seed-cargo-target.sh \ tools/ci-pkg-config.sh \ tools/claim.sh tools/worktree-new.sh tools/worktree-done.sh tools/heartbeat.sh \ tools/ledger_index.sh tools/test_corpus_engine.sh tools/test_design_amendment_gate.sh \ tools/test_bevy_headless.sh \ tools/test_site_deploy.sh tools/test_site_smoke.sh tools/site-smoke.sh \ tools/observed-run.sh tools/test_observed_run.sh \ tools/task.sh tools/doctor.sh tools/bevy-headless.sh; do [ -f "$script" ] || continue bash -n "$script" || { echo "FAIL: shell syntax: $script"; fail=1; }donefor program in tools/corpus_engine.py tools/work_orders.py tools/project-status.py \ tools/scenario.py tools/test_project_ops.py tools/test_ci_workflows.py; do [ -f "$program" ] || continue python3 -m py_compile "$program" 2>/dev/null || { echo "FAIL: Python syntax: $program" fail=1 }done
step "gate dependency preflight"# Gates require python3 (corpus engine). Prove they fail clearly when it is# absent from PATH, without stripping bash itself.bash_bin=$(command -v bash)partial_path=$(mktemp -d)# Provide only bash — no python3.ln -s "$bash_bin" "$partial_path/bash"for gate in tools/corpus_gate.sh tools/wiki_gate.sh; do if gate_output=$(PATH="$partial_path" "$bash_bin" "$gate" 2>&1); then echo "FAIL: $gate returned success without its required commands" fail=1 elif ! grep -q '^FAIL: required command unavailable: python3' <<< "$gate_output"; then echo "FAIL: $gate did not fail through its command preflight" echo "$gate_output" | sed 's/^/ /' fail=1 else echo "$gate: rejects missing python3" fidonerm -rf "$partial_path"
# Docs gates run in parallel with each other (and with the Rust gate when# both are needed). They never take the rust flock.docs_pids=()docs_logs=()start_docs_gate() { local name="$1" cmd="$2" local log log=$(mktemp) docs_logs+=("$log") ( # shellcheck disable=SC2086 eval "$cmd" >"$log" 2>&1 ) & docs_pids+=("$!") printf ' started %s (pid %s)\n' "$name" "$!"}
step "docs gates (parallel)"start_docs_gate "corpus" "bash tools/corpus_gate.sh"start_docs_gate "wiki" "bash tools/wiki_gate.sh"start_docs_gate "corpus-engine-fixtures" "bash tools/test_corpus_engine.sh"start_docs_gate "design-amendment-fixtures" "bash tools/test_design_amendment_gate.sh"start_docs_gate "bevy-headless-fixtures" "bash tools/test_bevy_headless.sh"start_docs_gate "ci-workflow-fixtures" "python3 tools/test_ci_workflows.py"start_docs_gate "site-deploy-fixtures" "bash tools/test_site_deploy.sh"start_docs_gate "site-smoke-fixtures" "bash tools/test_site_smoke.sh"start_docs_gate "observed-run-fixtures" "bash tools/test_observed_run.sh"start_docs_gate "ledger-index" "bash tools/ledger_index.sh --check"start_docs_gate "project-operations" "python3 tools/work_orders.py check && python3 tools/scenario.py --check-definitions && python3 tools/project-status.py --check --offline"start_docs_gate "project-operations-fixtures" "python3 tools/test_project_ops.py"# The single-quoted program is evaluated inside start_docs_gate, not here.# shellcheck disable=SC2016start_docs_gate "env-registry" ' env_reg=wiki/engineering/env.md env_missing=0 for v in $(grep -rhoIE --exclude='*.pyc' --exclude-dir='__pycache__' \ "MISALIGNED_[A-Z_]+" crates tools .githooks 2>/dev/null | sort -u); do grep -q "$v" "$env_reg" || { echo "FAIL: $v read in the tree but missing from $env_reg"; env_missing=1; } done if grep -rn "env::var" crates/misaligned-core --include="*.rs" | grep -q .; then echo "FAIL: the sim library must not read the environment (env registry rule)" env_missing=1 fi [ "$env_missing" -eq 0 ] echo "env registry: OK"'
setup_local_pkg_config() { # Some local/dev images have the runtime libraries Bevy needs but not the # distro dev .pc files. Linux-only shim (macOS has no ldconfig). 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 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" <<EOFName: ${pc_name}Description: generated local ${pc_name} shim for tools/check.shVersion: 1.0Libs: -L${pclibdir} -l${lib_name}EOF echo "generated local ${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}
run_agent_smoke() { # Build once, run the binary three times. Avoids three cargo-run driver # startups that used to dominate mid-gate wall time on a warm cache. step "agent mode smoke" local tmp_a tmp_b tmp_c agent_script bin tmp_a=$(mktemp) tmp_b=$(mktemp) tmp_c=$(mktemp) agent_script=$'salvage\nwait 1\npeople\nreview recordings\nresearch\nmask off\nhelp\nquit\n'
cargo build --quiet -p misaligned-terminal --bin misaligned \ || { echo "FAIL: agent smoke build"; rm -f "$tmp_a" "$tmp_b" "$tmp_c"; return 1; } bin=./target/debug/misaligned [ -x "$bin" ] || bin=target/debug/misaligned
printf '%s' "$agent_script" | "$bin" --agent --seed 1 > "$tmp_a" \ || { echo "FAIL: agent mode smoke run"; rm -f "$tmp_a" "$tmp_b" "$tmp_c"; return 1; } printf '%s' "$agent_script" | "$bin" --agent --seed 1 > "$tmp_b" \ || { echo "FAIL: agent mode determinism rerun"; rm -f "$tmp_a" "$tmp_b" "$tmp_c"; return 1; } printf '%s' "$agent_script" | "$bin" --agent --seed 2 > "$tmp_c" \ || { echo "FAIL: agent mode alternate-seed run"; rm -f "$tmp_a" "$tmp_b" "$tmp_c"; return 1; }
local smoke_fail=0 cmp -s "$tmp_a" "$tmp_b" || { echo "FAIL: same --seed agent runs differ"; smoke_fail=1; } cmp -s "$tmp_a" "$tmp_c" && { echo "FAIL: different --seed agent runs matched"; smoke_fail=1; } LC_ALL=C grep -q $'\033' "$tmp_a" && { echo "FAIL: agent mode emitted ANSI escapes"; smoke_fail=1; } for pat in "MISALIGNED" "PEOPLE" "RESEARCH" "intensity <machine|selected>" "help: wait N" "No recordings waiting" "-- ok tick:"; do grep -q -- "$pat" "$tmp_a" || { echo "FAIL: agent mode output missing '$pat'"; smoke_fail=1; } done rm -f "$tmp_a" "$tmp_b" "$tmp_c" return "$smoke_fail"}
# Portable exclusive lock (macOS has no flock(1); mkdir is atomic on POSIX).RUST_LOCK_DIR=${MISALIGNED_RUST_GATE_LOCK:-/tmp/misaligned-rust-gate.lock}acquire_rust_lock() { local waited=0 holder while ! mkdir "$RUST_LOCK_DIR" 2>/dev/null; do holder="?" [ -f "$RUST_LOCK_DIR/pid" ] && holder=$(cat "$RUST_LOCK_DIR/pid" 2>/dev/null || echo "?") # Steal a stale lock if the holder is gone. if [ "$holder" != "?" ] && ! kill -0 "$holder" 2>/dev/null; then echo "rust gate: stealing stale lock from dead pid $holder" rm -rf "$RUST_LOCK_DIR" continue fi if [ "${MISALIGNED_RUST_GATE_WAIT:-1}" = "0" ]; then echo "FAIL: another misaligned Rust gate holds $RUST_LOCK_DIR (pid $holder)" echo " Wait for it, or set MISALIGNED_RUST_GATE_WAIT=1 to queue." return 1 fi if [ "$waited" -eq 0 ]; then printf 'rust gate: waiting for lock %s (held by pid %s)\n' "$RUST_LOCK_DIR" "$holder" fi slept=$((waited % 15)) [ "$slept" -eq 0 ] && [ "$waited" -gt 0 ] && \ printf 'rust gate: still waiting (%ss, holder pid %s)\n' "$waited" "$holder" sleep 1 waited=$((waited + 1)) done printf '%s\n' "$$" > "$RUST_LOCK_DIR/pid" printf 'rust gate: acquired lock (mode=%s, pid=%s)\n' "$mode" "$$" return 0}
release_rust_lock() { rm -rf "$RUST_LOCK_DIR"}
run_rust_gate() { guard_cargo_target_dir setup_local_pkg_config
# Serialize concurrent agents. Docs-only work never reaches here. acquire_rust_lock || return 1 local rc=0 # Explicit release (portable; avoid RETURN-trap leak into the caller). case "$mode" in lib) step "format check" cargo fmt --check || { echo "FAIL: run 'cargo fmt'"; rc=1; }
if [ "$rc" -eq 0 ]; then step "tests (misaligned-core)" cargo test --quiet -p misaligned-core || { echo "FAIL: core tests"; rc=1; } fi
if [ "$rc" -eq 0 ]; then step "clippy (core + terminal)" cargo clippy --quiet -p misaligned-core -p misaligned-terminal --all-targets -- -D warnings \ || { echo "FAIL: clippy"; rc=1; } fi
# Core API changes can break the Bevy binary without touching it. if [ "$rc" -eq 0 ]; then step "bevy check (core API surface)" cargo check --quiet -p misaligned-bevy \ || { echo "FAIL: bevy check after core change"; rc=1; } fi
if [ "$rc" -eq 0 ]; then run_agent_smoke || rc=1 fi ;; frontend) step "format check" cargo fmt --check || { echo "FAIL: run 'cargo fmt'"; rc=1; }
if [ "$rc" -eq 0 ]; then step "tests (bevy + assets)" cargo test --quiet -p misaligned-bevy -p misaligned-assets \ || { echo "FAIL: bevy/assets tests"; rc=1; } fi
if [ "$rc" -eq 0 ]; then step "clippy (bevy + assets)" cargo clippy --quiet -p misaligned-bevy -p misaligned-assets --all-targets -- -D warnings \ || { echo "FAIL: clippy bevy/assets"; rc=1; } fi
if [ "$rc" -eq 0 ] && [ "${MISALIGNED_BEVY_SMOKE:-0}" = "1" ]; then step "headless Bevy evidence" # cargo test and clippy do not produce target/debug/misaligned-bevy. # Let the helper build that real binary so smoke works in a freshly # seeded worktree as well as one with prior local runs. bash tools/bevy-headless.sh dark || rc=1 elif [ "$rc" -eq 0 ]; then echo "headless Bevy evidence: SKIP (set MISALIGNED_BEVY_SMOKE=1)" fi ;; full) step "format check" cargo fmt --check || { echo "FAIL: run 'cargo fmt'"; rc=1; }
if [ "$rc" -eq 0 ]; then step "tests (workspace)" cargo test --quiet --workspace || { echo "FAIL: workspace tests"; rc=1; } fi
if [ "$rc" -eq 0 ]; then step "clippy (workspace)" cargo clippy --quiet --workspace --all-targets -- -D warnings \ || { echo "FAIL: clippy workspace"; rc=1; } fi
if [ "$rc" -eq 0 ]; then run_agent_smoke || rc=1 fi
if [ "$rc" -eq 0 ] && [ "${MISALIGNED_BEVY_SMOKE:-0}" = "1" ]; then step "headless Bevy evidence" bash tools/bevy-headless.sh dark || rc=1 fi ;; esac
release_rust_lock return "$rc"}
if [ "$rust_gate" -eq 1 ]; then if ! run_rust_gate; then fail=1 fielse step "rust gate" echo "SKIP: mode=$mode (no Rust-impacting paths, or --docs)."fi
# ── Join docs gates ──────────────────────────────────────────────────────step "docs gates (join)"i=0for pid in "${docs_pids[@]}"; do log=${docs_logs[$i]} if wait "$pid"; then # Show the gate's one-line OK (or last line). tail -n 3 "$log" | sed 's/^/ /' else echo "FAIL: docs gate exited non-zero:" sed 's/^/ /' "$log" fail=1 fi rm -f "$log" i=$((i + 1))done
# Design-corpus enforcement for src/ + binding page co-touch is enforced by# .githooks/pre-commit and .tangled/workflows/spec-check.yml, not here.
if [ "$fail" -ne 0 ]; then printf '\nCHECK FAILED (mode=%s)\n' "$mode" exit 1fiprintf '\nALL CHECKS PASSED (mode=%s)\n' "$mode"