#!/usr/bin/env bash # tooling/godot-cold-parse [--run-menu] — cold-cache headless parse check. # # Used by /pr-process step 1c before push. Deletes the cached script-class # registry so the parse simulates the cold-start ordering CI / fresh clones # see: Sprint 36 close caught a new `class_name MetaScreen` base class and # six extending scripts that parsed fine on warm developer caches but hit # `Could not find base class "MetaScreen"` post-merge, because the # autoload-vs-class_name registration order only resolves correctly once the # class cache is seeded (see CLAUDE.md -> GDScript conventions -> Autoload # parse-order rule). # # Godot's resource scanner emits category errors (e.g. "Export type can only # be built-in, a resource, a node, or an enum") that do NOT always prefix # with SCRIPT ERROR — they appear as plain ERROR lines. The filter below # catches both, then drops known pre-existing noise from the autoload # class_name parse-order trap. Sprint 36 shipped a scanner error the old # narrower grep missed; this is why the filter stays wide. # # --run-menu: also launch main_menu.tscn briefly (for branches with UI changes). # # Exit 0 + "clean" if no matches. Exit 1 + the matched lines if any are found. set -euo pipefail REPO_ROOT="$(git rev-parse --show-toplevel)" RUN_MENU=false [ "${1:-}" = "--run-menu" ] && RUN_MENU=true rm -f "$REPO_ROOT/client/.godot/global_script_class_cache.cfg" # A truly cold checkout (fresh clone or worktree — .godot/ is gitignored) has # no resource-import cache, and every imported asset (fonts, ogg) then "fails # loading" during the parse run: a wall of false positives. Seed the cache # with an import pass first; source assets are tracked, so this is always # reconstructible. (Found live: first run in a fresh worktree, 2026-07-13.) if [ ! -d "$REPO_ROOT/client/.godot/imported" ] || [ -z "$(ls -A "$REPO_ROOT/client/.godot/imported" 2>/dev/null)" ]; then echo "godot-cold-parse: no import cache — running one-time import pass..." >&2 set +e IMPORT_OUT=$(godot --headless --path "$REPO_ROOT/client" --import 2>&1) IMPORT_EXIT=$? set -e if [ "$IMPORT_EXIT" -ne 0 ]; then echo "godot-cold-parse: import pass exited $IMPORT_EXIT" >&2 printf '%s\n' "$IMPORT_OUT" | tail -20 >&2 exit "$IMPORT_EXIT" fi fi FILTER='^(SCRIPT )?ERROR|Parse Error|Export type' # Capture the godot run separately from the filter pipeline: with the # trailing `|| true` on the greps, a nonzero exit from godot itself (crash, # missing binary, corrupted install) would otherwise report "clean". Nothing # downstream re-reads the raw output now that this is scripted, so fail loud. set +e RAW=$(godot --headless --path "$REPO_ROOT/client" --quit 2>&1) GODOT_EXIT=$? set -e if [ "$GODOT_EXIT" -ne 0 ]; then echo "godot-cold-parse: godot itself exited $GODOT_EXIT — not a parse verdict" >&2 printf '%s\n' "$RAW" | tail -20 >&2 exit "$GODOT_EXIT" fi MATCHES=$(printf '%s\n' "$RAW" \ | grep -iE "$FILTER" \ | grep -v "Failed loading resource: res://assets" \ | grep -v "Cannot infer the type" \ | grep -vE '(Messagepack|LocalBridge|ServerProcess|Constants)" not declared' || true) if [ "$RUN_MENU" = true ]; then # Deliberately no exit-code check here: `timeout` kills the menu after # 10s by design (exit 124 is the expected shutdown path); only the # scraped error lines carry signal for this bounded run. MENU_MATCHES=$(timeout 10 godot --path "$REPO_ROOT/client" res://scenes/main_menu.tscn 2>&1 \ | grep -iE "$FILTER" || true) if [ -n "$MENU_MATCHES" ]; then MATCHES="$MATCHES $MENU_MATCHES" fi fi if [ -n "$MATCHES" ]; then echo "$MATCHES" exit 1 fi # Restore a FULL class cache before exiting: the cold parse run re-seeds # global_script_class_cache.cfg only partially — addon classes (e.g. gdUnit4's # GdUnitTestCIRunner) are missing, which leaves tests/run-godot unable to even # start (0 tests in ~350ms; found live when the pre-push gate ran the suite # right after this script, 2026-07-14). The cold verdict above is already # decided; this restore just returns the tree to a runnable state. godot --headless --path "$REPO_ROOT/client" --import > /dev/null 2>&1 || true echo "godot-cold-parse: clean"