learning_ai_invt_trdg/scripts/ui-drift-audit.sh

76 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env sh
set -eu
MODE="${1:-report}"
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd -P)"
WEB_SRC="${ROOT_DIR}/web/src"
if [ ! -d "${WEB_SRC}" ]; then
echo "error: web source directory not found: ${WEB_SRC}" >&2
exit 2
fi
python3 - "${WEB_SRC}" "${MODE}" <<'PY'
import pathlib
import re
import sys
web_src = pathlib.Path(sys.argv[1])
mode = sys.argv[2]
extensions = {".ts", ".tsx", ".css"}
excluded_suffixes = (".test.ts", ".test.tsx", ".dom.test.tsx")
excluded_parts = {"test", "assets"}
patterns = [
("raw interactive controls outside approved primitives", "Raw Interactive Controls", re.compile(r"<(button|input|textarea|select)(\s|>)"), True),
("legacy global surface classes", "Legacy Global Surface Classes", re.compile(r"\b(surface-card|surface-muted|badge|input-shell)\b"), False),
("hardcoded color literals", "Hardcoded Color Literals", re.compile(r"(#[0-9a-f]{3,8}\b|rgba?\()", re.IGNORECASE), False),
("direct @bytelyst/ui imports outside product adapter", "Direct Common UI Imports Outside Adapter", re.compile(r"@bytelyst/ui"), True),
]
def included(path: pathlib.Path) -> bool:
if path.suffix not in extensions:
return False
name = path.name
if name.endswith(excluded_suffixes):
return False
rel_parts = set(path.relative_to(web_src).parts)
return rel_parts.isdisjoint(excluded_parts)
def approved_ui(path: pathlib.Path) -> bool:
return "components" in path.parts and "ui" in path.parts
def collect(pattern: re.Pattern[str], exclude_approved_ui: bool) -> list[tuple[str, int, str]]:
matches = []
for path in sorted(web_src.rglob("*")):
if not path.is_file() or not included(path):
continue
if exclude_approved_ui and approved_ui(path):
continue
try:
lines = path.read_text(encoding="utf-8").splitlines()
except UnicodeDecodeError:
continue
rel = path.relative_to(web_src).as_posix()
for line_number, line in enumerate(lines, start=1):
if pattern.search(line):
matches.append((rel, line_number, line.rstrip()))
return matches
results = [(label, title, collect(pattern, exclude_approved_ui)) for label, title, pattern, exclude_approved_ui in patterns]
total = sum(len(matches) for _, _, matches in results)
print(f"UI drift audit mode={mode}")
for label, _, matches in results:
print(f"{label}: {len(matches)}")
for _, title, matches in results:
print(f"\n== {title} ==")
for rel, line_number, line in matches:
print(f"{rel}:{line_number}:{line}")
if mode == "strict" and total > 0:
print(f"error: strict UI drift audit failed with {total} findings", file=sys.stderr)
sys.exit(1)
PY