|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: MPL-2.0 |
| 3 | +# Language-registry consistency guard. |
| 4 | +# |
| 5 | +# nextgen-languages registers its language family across several surfaces. They |
| 6 | +# drifted badly before (a language present on one surface, missing on another), |
| 7 | +# so this check fails the build when they disagree. |
| 8 | +# |
| 9 | +# Source of truth: the human-facing pointers in `languages/<id>.md`. Every other |
| 10 | +# surface MUST list exactly that same set of ids: |
| 11 | +# - .machine_readable/LANGUAGES.a2ml ([[language]] stanzas; forward-looking entries with |
| 12 | +# status "proposed"/"exploratory" are exempt — they |
| 13 | +# are not yet committed and carry no languages/ pointer) |
| 14 | +# - hooks/validate-coordinator-boundary.sh (the LANGS re-vendoring guard list) |
| 15 | +# - .machine_readable/6a2/ECOSYSTEM.a2ml ([language-repos] keys) |
| 16 | +# - language-status-tracker.jl (const LANGUAGES — must be a SUPERSET; |
| 17 | +# it also tracks playgrounds/ecosystem/umbrella) |
| 18 | +# |
| 19 | +# See 0-AI-MANIFEST.a2ml and EXTRACTION-MANIFEST.md. |
| 20 | +set -euo pipefail |
| 21 | + |
| 22 | +cd "$(git rev-parse --show-toplevel)" |
| 23 | + |
| 24 | +ERRORS=0 |
| 25 | + |
| 26 | +# ── Source of truth: languages/<id>.md pointers (excluding README) ──────────── |
| 27 | +family() { |
| 28 | + for f in languages/*.md; do |
| 29 | + b="$(basename "$f" .md)" |
| 30 | + [ "$b" = "README" ] || printf '%s\n' "$b" |
| 31 | + done | sort -u |
| 32 | +} |
| 33 | + |
| 34 | +# ── LANGUAGES.a2ml: committed [[language]] ids (status not proposed/exploratory) ── |
| 35 | +a2ml_registered() { |
| 36 | + awk ' |
| 37 | + /^\[\[language\]\]/ { if (id != "") print id, status; id=""; status="" ; next } |
| 38 | + /^id[ \t]*=/ { line=$0; sub(/^id[ \t]*=[ \t]*"/,"",line); sub(/".*/,"",line); id=line } |
| 39 | + /^status[ \t]*=/ { line=$0; sub(/^status[ \t]*=[ \t]*"/,"",line); sub(/".*/,"",line); status=line } |
| 40 | + END { if (id != "") print id, status } |
| 41 | + ' .machine_readable/LANGUAGES.a2ml \ |
| 42 | + | awk '$2 != "proposed" && $2 != "exploratory" { print $1 }' | sort -u |
| 43 | +} |
| 44 | + |
| 45 | +# ── boundary-hook LANGS list ────────────────────────────────────────────────── |
| 46 | +hook_langs() { |
| 47 | + grep -E '^LANGS=' hooks/validate-coordinator-boundary.sh \ |
| 48 | + | sed -E 's/^LANGS="//; s/"[[:space:]]*$//' \ |
| 49 | + | tr ' ' '\n' | sed '/^$/d' | sort -u |
| 50 | +} |
| 51 | + |
| 52 | +# ── ECOSYSTEM.a2ml [language-repos] keys (excluding the `note` key) ─────────── |
| 53 | +ecosystem_keys() { |
| 54 | + awk ' |
| 55 | + /^\[language-repos\]/ { inblk=1; next } |
| 56 | + /^\[/ { inblk=0 } |
| 57 | + inblk && /^[^#].*=/ { |
| 58 | + key=$0; sub(/[ \t]*=.*/,"",key); gsub(/["[:space:]]/,"",key) |
| 59 | + if (key != "note" && key != "") print key |
| 60 | + } |
| 61 | + ' .machine_readable/6a2/ECOSYSTEM.a2ml | sort -u |
| 62 | +} |
| 63 | + |
| 64 | +# ── tracker const LANGUAGES vector (quoted ids) ─────────────────────────────── |
| 65 | +tracker_langs() { |
| 66 | + awk '/const LANGUAGES[ \t]*=[ \t]*\[/ { inblk=1; next } inblk && /^\]/ { inblk=0 } inblk' \ |
| 67 | + language-status-tracker.jl \ |
| 68 | + | grep -oE '"[^"]+"' | tr -d '"' | sort -u |
| 69 | +} |
| 70 | + |
| 71 | +FAMILY="$(family)" |
| 72 | +COUNT="$(printf '%s\n' "$FAMILY" | wc -l | tr -d ' ')" |
| 73 | +printf 'language-registry: source of truth = languages/*.md (%s entries)\n' "$COUNT" |
| 74 | + |
| 75 | +# report_diff <surface-name> <surface-set> <mode: exact|superset> |
| 76 | +report_diff() { |
| 77 | + local name="$1" have="$2" mode="$3" missing extra |
| 78 | + missing="$(comm -23 <(printf '%s\n' "$FAMILY") <(printf '%s\n' "$have") || true)" |
| 79 | + if [ -n "$missing" ]; then |
| 80 | + printf 'ERROR: %s is MISSING family languages:\n' "$name" |
| 81 | + printf '%s\n' "$missing" | sed 's/^/ - /' |
| 82 | + ERRORS=$((ERRORS + 1)) |
| 83 | + fi |
| 84 | + if [ "$mode" = "exact" ]; then |
| 85 | + extra="$(comm -13 <(printf '%s\n' "$FAMILY") <(printf '%s\n' "$have") || true)" |
| 86 | + if [ -n "$extra" ]; then |
| 87 | + printf 'ERROR: %s lists languages NOT in languages/ (add a pointer, or remove them):\n' "$name" |
| 88 | + printf '%s\n' "$extra" | sed 's/^/ - /' |
| 89 | + ERRORS=$((ERRORS + 1)) |
| 90 | + fi |
| 91 | + fi |
| 92 | +} |
| 93 | + |
| 94 | +report_diff ".machine_readable/LANGUAGES.a2ml" "$(a2ml_registered)" exact |
| 95 | +report_diff "hooks/validate-coordinator-boundary.sh LANGS" "$(hook_langs)" exact |
| 96 | +report_diff ".machine_readable/6a2/ECOSYSTEM.a2ml" "$(ecosystem_keys)" exact |
| 97 | +report_diff "language-status-tracker.jl const LANGUAGES" "$(tracker_langs)" superset |
| 98 | + |
| 99 | +if [ "$ERRORS" -gt 0 ]; then |
| 100 | + printf '\nlanguage-registry check FAILED (%s surface(s) out of sync).\n' "$ERRORS" |
| 101 | + printf 'Every language must appear on all registry surfaces. Update them together —\n' |
| 102 | + printf 'the canonical set is languages/*.md; see .machine_readable/LANGUAGES.a2ml.\n' |
| 103 | + exit 1 |
| 104 | +fi |
| 105 | + |
| 106 | +printf 'language-registry: OK (all surfaces agree on the %s-language family)\n' "$COUNT" |
| 107 | +exit 0 |
0 commit comments