#!/usr/bin/env bash # The objective gate. No opinions, only exit codes. # Usage: ./scripts/verify.sh set -uo pipefail # --list answers "does this gate actually exist?" without reading the script. Docs that claim a gate are # checkable against it. The pattern is written pas[s] so this grep does not match its own source line. if [ "${1:-}" = "--list" ]; then printf 'gates in %s:\n' "$0" grep -oE 'pas[s] "[^"]+"' "$0" | awk -F'"' '{print $2}' | sed 's/\$([^)]*)/…/g; s/\$[A-Za-z_][A-Za-z_0-9]*/…/g; s/^/ /' | sort -u exit 0 fi cd "$(dirname "$0")/.." || exit 1 [ -f scripts/budgets.env ] && . scripts/budgets.env CORE_LOC_MAX=${CORE_LOC_MAX:-2000} EXT_LOC_MAX=${EXT_LOC_MAX:-2000} FILE_LOC_WARN=${FILE_LOC_WARN:-500} FUNC_LOC_WARN=${FUNC_LOC_WARN:-60} DEPS_MAX=${DEPS_MAX:-6} fail=0 warn=0 pass() { printf ' ok %s\n' "$1"; } bad() { printf ' FAIL %s\n' "$1"; fail=1; } note() { printf ' warn %s\n' "$1"; warn=$((warn + 1)); } head_() { printf '\n%s\n' "$1"; } if ! command -v go >/dev/null 2>&1 || ! command -v gofmt >/dev/null 2>&1; then echo "FAIL go toolchain not found on PATH — cannot verify anything" exit 1 fi # --- documentation coupling ------------------------------------------------- # Runs before the Go gates so it still applies while the repo is harness-only. # Code and the docs describing it move together, or they drift apart silently. head_ "documentation" if [ -d .git ] && command -v git >/dev/null 2>&1; then # -uall matters: the default collapses untracked directories to "dir/", which would hide a # brand-new internal/ext/feeds/feeds.go from the .go check below. changed=$(git status --porcelain -uall 2>/dev/null | sed 's/^...//' | sed 's/.* -> //') if [ -z "$changed" ]; then pass "working tree clean — nothing to couple" else if echo "$changed" | grep -qE '\.go$' && ! echo "$changed" | grep -qx 'docs/state.md'; then bad "*.go changed but docs/state.md did not — inventory, counters and the verified-against line move with the code" else pass "code/state.md coupling" fi if echo "$changed" | grep -qE '^(cmd|internal)/.*\.go$' && ! echo "$changed" | grep -qE '_test\.go$'; then # A comment-only or gofmt-only diff ships no behaviour, so it owes no test. Compare the added and # removed lines with comments, blanks and indentation stripped: equal sets mean nothing happened. gochanged=$(echo "$changed" | grep -E '^(cmd|internal)/.*\.go$') godiff=$(git diff HEAD -- $gochanged 2>/dev/null) # strip the marker, comments, blanks, and *all* whitespace runs — gofmt realignment then compares # equal, while any real edit still differs. strip() { grep -E "^[$1]" | grep -vE '^(\+\+\+|---)' | sed "s/^[$1]//" | grep -vE '^[[:space:]]*(//|/\*|\*/|\* )' | tr -s ' \t' ' ' | sed 's/^ //; s/ $//' | grep -v '^$' | sort; } newgo=$(git ls-files --others --exclude-standard -- $gochanged 2>/dev/null) if [ -z "$newgo" ] && [ "$(echo "$godiff" | strip '+')" = "$(echo "$godiff" | strip '-')" ]; then pass "code/test coupling (comment- or format-only)" else bad "cmd/ or internal/ .go changed but no _test.go did — behaviour ships with a test (conventions.md)" fi else pass "code/test coupling" fi if echo "$changed" | grep -qE '^internal/.*templates/.*\.html$' && ! echo "$changed" | grep -qx 'docs/theme-contract.md'; then bad "embedded templates changed but docs/theme-contract.md did not — they drift together (ADR-0023)" else pass "templates/theme-contract coupling" fi if echo "$changed" | grep -qE '^(CLAUDE\.md$|scripts/|\.claude/)' && ! echo "$changed" | grep -qx 'HARNESS.md'; then bad "the harness changed (CLAUDE.md, scripts/ or .claude/) but HARNESS.md did not — the guide to the machine is part of the machine" else pass "harness/HARNESS.md coupling" fi fi # The reference theme is a contract demonstration, not a design: zero JavaScript (ADR-0026). themefiles=$(find internal -path '*templates*' -name '*.html' 2>/dev/null || true) if [ -n "$themefiles" ]; then scripted=$(echo "$themefiles" | xargs grep -ln '/dev/null || true) if [ -n "$scripted" ]; then bad "