#!/usr/bin/env bash set -euo pipefail STRICT=0 if [[ "${1:-}" == "--strict" ]]; then STRICT=1 fi ROOT="$(git rev-parse --show-toplevel)" cd "$ROOT" # Detect whether ripgrep is available. Fall back to grep so the audit cannot # silently pass on hosts without rg (which was hiding regressions until now). if command -v rg >/dev/null 2>&1; then SEARCH_BACKEND="rg" else SEARCH_BACKEND="grep" echo "[ui-drift-audit] note: ripgrep not found, falling back to grep" >&2 fi # Build a search command that maps rg --glob exclusions to grep --exclude. search() { local pattern="$1" shift if [[ "$SEARCH_BACKEND" == "rg" ]]; then rg -n "$pattern" "$@" --glob '!**/*.test.*' || true else # grep equivalent: -rnE with --exclude for test files. Translate # rg-style --glob '!path' arguments into grep --exclude entries so the # caller's exclusions (e.g. the @bytelyst/ui adapter file) still apply. local targets=() local extra_excludes=() while [[ $# -gt 0 ]]; do case "$1" in --glob) local glob_arg="${2:-}" if [[ "$glob_arg" == "!"* ]]; then local glob_path="${glob_arg#!}" # grep --exclude matches against the basename, so reduce the # exclusion to a filename pattern. If callers exclude paths # like 'web/src/components/ui/Primitives.tsx', the basename # 'Primitives.tsx' is the exclusion key. extra_excludes+=("--exclude=$(basename "$glob_path")") fi shift 2 ;; *) targets+=("$1") shift ;; esac done grep -rnE "$pattern" "${targets[@]}" \ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \ --exclude="*.test.*" --exclude="*.spec.*" \ --exclude-dir=node_modules --exclude-dir=.next \ ${extra_excludes[@]+"${extra_excludes[@]}"} 2>/dev/null || true fi } report() { local title="$1" local pattern="$2" shift 2 local matches matches="$(search "$pattern" "$@")" echo "=== $title ===" if [[ -z "$matches" ]]; then echo "ok: no matches" return 0 fi echo "$matches" echo return 1 } failures=0 report "Raw interactive controls" '&2 exit 1 fi echo "UI drift audit completed with $failures non-empty category/categories."