The architecture check scans tools/ for duplicate class names and fails when game/ or content/ references res://tools/. The three tools scripts drop their global class_name (every caller preloads them), and ADR 0012 records that tools/ depends on the domain only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
4.1 KiB
Bash
Executable File
132 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIRECTORY="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
PROJECT_DIRECTORY="$(CDPATH= cd -- "$SCRIPT_DIRECTORY/.." && pwd)"
|
|
cd "$PROJECT_DIRECTORY"
|
|
|
|
failure_count=0
|
|
|
|
fail_with_matches() {
|
|
local label="$1"
|
|
local matches="$2"
|
|
if [ -n "$matches" ]; then
|
|
printf 'ARCHITECTURE ERROR: %s\n%s\n' "$label" "$matches"
|
|
failure_count=$((failure_count + 1))
|
|
fi
|
|
}
|
|
|
|
if ! rg -q 'config/features=.*"Forward Plus"' project.godot; then
|
|
fail_with_matches \
|
|
"production project must declare Forward Plus" \
|
|
"project.godot does not declare the Forward Plus feature"
|
|
fi
|
|
|
|
renderer_overrides="$(rg -n '^renderer/rendering_method' project.godot || true)"
|
|
fail_with_matches "production project overrides its Forward+ renderer" "$renderer_overrides"
|
|
|
|
autoload_entries="$({
|
|
awk '
|
|
/^\[autoload\]$/ { in_autoload = 1; next }
|
|
/^\[/ { in_autoload = 0 }
|
|
in_autoload && /^[^;[:space:]][^=]*=/ { print NR ":" $0 }
|
|
' project.godot
|
|
} || true)"
|
|
fail_with_matches "custom autoloads require an ADR" "$autoload_entries"
|
|
|
|
source_directories="src tests"
|
|
if [ -d game ]; then
|
|
source_directories="$source_directories game"
|
|
fi
|
|
if [ -d tools ]; then
|
|
source_directories="$source_directories tools"
|
|
fi
|
|
|
|
duplicate_classes="$(
|
|
rg --no-filename -o \
|
|
'^[[:space:]]*class_name[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' \
|
|
$source_directories 2>/dev/null \
|
|
| awk '{print $2}' \
|
|
| sort \
|
|
| uniq -d \
|
|
|| true
|
|
)"
|
|
fail_with_matches "duplicate class_name declarations" "$duplicate_classes"
|
|
|
|
if [ -d game ]; then
|
|
prototype_imports="$(rg -n 'res://(src|scenes)/' game --glob '*.gd' || true)"
|
|
fail_with_matches "production imports prototype code or scenes" "$prototype_imports"
|
|
|
|
test_imports="$(rg -n 'res://tests/' game --glob '*.gd' || true)"
|
|
fail_with_matches "production imports test code" "$test_imports"
|
|
|
|
tool_imports="$(
|
|
rg -n 'res://tools/' game content --glob '*.gd' --glob '*.tscn' --glob '*.tres' 2>/dev/null || true
|
|
)"
|
|
fail_with_matches "production imports authoring tools" "$tool_imports"
|
|
|
|
poc_references="$(rg -n 'poc_[ab]|POC_[AB]' game --glob '*.gd' --glob '*.tscn' || true)"
|
|
fail_with_matches "production contains a prototype dependency" "$poc_references"
|
|
|
|
if [ -d game/domain ]; then
|
|
domain_nodes="$(
|
|
rg -n '^extends[[:space:]]+(Node|Node2D|Node3D|Control|CanvasLayer|CharacterBody3D|SubViewport)' \
|
|
game/domain --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "domain extends scene-tree or presentation nodes" "$domain_nodes"
|
|
|
|
domain_dependencies="$(
|
|
rg -n 'res://game/(application|world|presentation|infrastructure|composition)/' \
|
|
game/domain --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "domain depends on an outer layer" "$domain_dependencies"
|
|
fi
|
|
|
|
if [ -d game/application ]; then
|
|
application_dependencies="$(
|
|
rg -n 'res://game/(world|presentation|infrastructure|composition)/' \
|
|
game/application --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "application depends on an adapter layer" "$application_dependencies"
|
|
fi
|
|
|
|
if [ -d game/world ]; then
|
|
world_dependencies="$(
|
|
rg -n 'res://game/(presentation|infrastructure|composition)/' \
|
|
game/world --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "world depends on presentation or infrastructure" "$world_dependencies"
|
|
fi
|
|
|
|
if [ -d game/presentation ]; then
|
|
presentation_dependencies="$(
|
|
rg -n 'res://game/(world|infrastructure|composition)/' \
|
|
game/presentation --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "presentation depends on a concrete adapter" "$presentation_dependencies"
|
|
fi
|
|
|
|
if [ -d game/infrastructure ]; then
|
|
infrastructure_dependencies="$(
|
|
rg -n 'res://game/(world|presentation|composition)/' \
|
|
game/infrastructure --glob '*.gd' || true
|
|
)"
|
|
fail_with_matches "infrastructure depends on world or presentation" "$infrastructure_dependencies"
|
|
fi
|
|
|
|
input_violations="$(
|
|
rg -n '\bInput\.' game --glob '*.gd' \
|
|
| sed 's#\\#/#g' \
|
|
| rg -v '^game/infrastructure/input/' \
|
|
|| true
|
|
)"
|
|
fail_with_matches "direct Input access outside infrastructure/input" "$input_violations"
|
|
fi
|
|
|
|
if [ "$failure_count" -ne 0 ]; then
|
|
printf 'Architecture checks: %d failure(s).\n' "$failure_count"
|
|
exit 1
|
|
fi
|
|
|
|
printf 'Architecture checks: passed.\n'
|