ci(antipattern): TS check reads .claude/CLAUDE.md exemption table #72
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: PMPL-1.0-or-later | |
| name: RSR Language Policy | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| check-banned-patterns: | |
| name: Check for banned languages/patterns | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 | |
| - name: Check for TypeScript | |
| run: | | |
| python3 << 'PYEOF' | |
| import re, sys, fnmatch, pathlib | |
| # Universal builtin allowlist — bridges that need no per-repo declaration. | |
| # Files matching any of these patterns are always allowed. | |
| BUILTIN_GLOBS = [ | |
| '*.d.ts', | |
| '**/bindings/**', | |
| '**/tests/**', '**/test/**', | |
| '**/scripts/**', | |
| '**/mcp-adapter/**', | |
| '**/*vscode*/**', | |
| '**/cli/**', | |
| '**/mod.ts', | |
| '**/lsp-server.ts', '**/lsp_server.ts', '**/lsp.ts', '**/*-lsp.ts', | |
| '**/deno-*/**', | |
| '**/node_modules/**', | |
| '**/vendor/**', | |
| '**/examples/**', | |
| '**/ffi/**', | |
| ] | |
| # Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table. | |
| # Single source of truth — adding a row here unblocks CI for that path. | |
| # Format expected: | |
| # ### TypeScript Exemptions ... | |
| # | Path | Files | Rationale | Unblock condition | | |
| # |---|---|---|---| | |
| # | `path/to/file.ts` | 1 | ... | ... | | |
| # | `dir/*.ts` | 6 | ... | ... | | |
| exemptions = [] | |
| claude_md = pathlib.Path('.claude/CLAUDE.md') | |
| if claude_md.exists(): | |
| in_table = False | |
| for line in claude_md.read_text(encoding='utf-8').splitlines(): | |
| if re.search(r'TypeScript [Ee]xemptions', line): | |
| in_table = True | |
| continue | |
| if in_table and line.startswith(('### ', '## ', '# ')): | |
| break | |
| if in_table and line.startswith('|'): | |
| m = re.match(r'\|\s*`([^`]+)`', line) | |
| if m: | |
| exemptions.append(m.group(1)) | |
| # Find all .ts and .tsx files | |
| found = [] | |
| for ext in ('ts', 'tsx'): | |
| found.extend(str(p) for p in pathlib.Path('.').rglob(f'*.{ext}')) | |
| def allowed(path): | |
| p = path.lstrip('./') | |
| for g in BUILTIN_GLOBS + exemptions: | |
| if fnmatch.fnmatchcase(p, g): | |
| return True | |
| # also treat glob ending with / as a directory prefix | |
| base = g.rstrip('/').rstrip('*').rstrip('/') | |
| if base and (p == base or p.startswith(base + '/')): | |
| return True | |
| return False | |
| bad = sorted(f for f in found if not allowed(f)) | |
| if bad: | |
| print("❌ TypeScript files detected outside the allowlist.\n") | |
| for f in bad: | |
| print(f" {f}") | |
| print() | |
| print("To resolve, either:") | |
| print(" (a) migrate the file to AffineScript") | |
| print(" (see Human_Programming_Guide.adoc migration chapter), OR") | |
| print(" (b) move it to an allowlisted bridge path") | |
| print(" (bindings/, tests/, scripts/, mcp-adapter/, *vscode*/, cli/, deno-*/, etc.), OR") | |
| print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md") | |
| print(" with rationale + unblock condition.") | |
| if exemptions: | |
| print(f"\n(Currently {len(exemptions)} exemption(s) parsed from .claude/CLAUDE.md.)") | |
| sys.exit(1) | |
| print(f"✅ No TypeScript files outside allowlist ({len(exemptions)} per-repo exemption(s) parsed).") | |
| PYEOF | |
| - name: Check for Go | |
| run: | | |
| if find . -name "*.go" | grep -q .; then | |
| echo "::error::Go files found! Use Rust instead (per RSR policy)" | |
| exit 1 | |
| fi | |
| echo "✓ No Go files found" | |
| - name: Check for npm/Node artifacts | |
| run: | | |
| if [ -f "package-lock.json" ] || [ -d "node_modules" ]; then | |
| echo "::error::npm artifacts found! Use Deno instead (per RSR policy)" | |
| exit 1 | |
| fi | |
| echo "✓ No npm artifacts found" | |
| - name: Check for Python (non-SaltStack) | |
| run: | | |
| banned_py=$(find . -name "*.py" | grep -v -E "(salt|pillar|states|_modules|_states)" | head -1) | |
| if [ -n "$banned_py" ]; then | |
| echo "::error::Python files found outside SaltStack! Use ReScript/Rust (per RSR policy)" | |
| exit 1 | |
| fi | |
| echo "✓ No banned Python files found" | |
| - name: Check for Makefiles | |
| run: | | |
| if [ -f "Makefile" ] || [ -f "makefile" ] || find . -name "*.mk" | grep -q .; then | |
| echo "::error::Makefile found! Use Justfile or Mustfile instead (per RSR policy)" | |
| exit 1 | |
| fi | |
| echo "✓ No Makefiles found" |