magicciv/tools/check-abstract-state-docs.sh
Natalie 131594d334 feat(@projects/@magic-civilization): implement wonder-tracking ai evaluation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-17 01:45:23 -07:00

73 lines
2.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# Guard the `mc-ai::abstract_state` docs contract.
#
# The AbstractRolloutState POD is the Rust↔WGSL interface. Every public field's
# docstring must include its byte offset (e.g. `@24`) so readers of either side
# can cross-reference without opening the other. `deny(missing_docs)` catches
# *missing* docs; this script catches docs that drift off the offset contract.
#
# Run from EDIT host (pure text scan, no toolchain needed):
# bash tools/check-abstract-state-docs.sh
#
# On RUN host, `cargo doc -p mc-ai --no-deps` also enforces the `deny` at build
# time — that's the authoritative gate. This script is the fast local check.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FILE="$REPO_ROOT/src/simulator/crates/mc-ai/src/abstract_state.rs"
if [[ ! -f "$FILE" ]]; then
echo "ERROR: $FILE not found" >&2
exit 2
fi
# Every public field (including `_padN`) must have `@<offset>` somewhere in its
# doc comments. We walk the AbstractPlayerState struct body and flag any
# `pub <name>: <type>,` line whose preceding doc block is missing `@\d+`.
python3 - "$FILE" <<'PY'
import re
import sys
from pathlib import Path
src = Path(sys.argv[1]).read_text()
# Isolate the AbstractPlayerState struct body.
m = re.search(
r"pub struct AbstractPlayerState \{(.*?)\n\}",
src,
re.DOTALL,
)
if not m:
print("ERROR: could not locate AbstractPlayerState struct body", file=sys.stderr)
sys.exit(2)
body = m.group(1)
# Walk lines, track the current doc block, check each `pub <field>: <type>,`.
lines = body.splitlines()
doc_block: list[str] = []
failures: list[tuple[str, str]] = []
for raw in lines:
line = raw.strip()
if line.startswith("///"):
doc_block.append(line)
continue
field_m = re.match(r"pub (\w+):", line)
if field_m:
field = field_m.group(1)
doc_text = " ".join(doc_block)
if not re.search(r"@\d+", doc_text):
failures.append((field, doc_text or "<no doc>"))
doc_block = []
elif line and not line.startswith("//"):
# Non-doc, non-field line — reset the doc block.
doc_block = []
if failures:
print("FAIL: AbstractPlayerState fields missing `@<offset>` in docstring:")
for field, doc in failures:
print(f" - {field}: {doc[:80]}")
sys.exit(1)
print("OK: every AbstractPlayerState field docstring includes a byte offset.")
PY