Skip to content

ci(antipattern): TS check reads .claude/CLAUDE.md exemption table #196

ci(antipattern): TS check reads .claude/CLAUDE.md exemption table

ci(antipattern): TS check reads .claude/CLAUDE.md exemption table #196

Workflow file for this run

# SPDX-License-Identifier: PMPL-1.0-or-later
# RSR Anti-Pattern CI Check
# SPDX-License-Identifier: PMPL-1.0-or-later
#
# Enforces: No TypeScript, No Go, No Python (except SaltStack), No npm
# Allows: ReScript, Deno, WASM, Rust, OCaml, Haskell, Guile/Scheme
name: RSR Anti-Pattern Check
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
permissions: read-all
jobs:
antipattern-check:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- 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 "❌ Go files detected - use Rust/WASM instead"
find . -name "*.go"
exit 1
fi
echo "✅ No Go files"
- name: Check for Python (non-SaltStack)
run: |
PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states | grep -v _modules | grep -v pillar | grep -v venv | grep -v __pycache__ || true)
if [ -n "$PY_FILES" ]; then
echo "❌ Python files detected - only allowed for SaltStack"
echo "$PY_FILES"
exit 1
fi
echo "✅ No non-SaltStack Python files"
- name: Check for npm lockfiles
run: |
if [ -f "package-lock.json" ] || [ -f "yarn.lock" ]; then
echo "❌ npm/yarn lockfile detected - use Deno instead"
exit 1
fi
echo "✅ No npm lockfiles"
- name: Check for tsconfig
run: |
if [ -f "tsconfig.json" ]; then
echo "❌ tsconfig.json detected - use ReScript instead"
exit 1
fi
echo "✅ No tsconfig.json"
- name: Verify Deno presence (if package.json exists)
run: |
if [ -f "package.json" ]; then
if [ ! -f "deno.json" ] && [ ! -f "deno.jsonc" ]; then
echo "⚠️ Warning: package.json without deno.json - migration recommended"
fi
fi
echo "✅ Deno configuration check complete"
- name: Summary
run: |
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ RSR Anti-Pattern Check Passed ✅ ║"
echo "║ ║"
echo "║ Allowed: ReScript, Deno, WASM, Rust, OCaml, Haskell, ║"
echo "║ Guile/Scheme, SaltStack (Python) ║"
echo "║ ║"
echo "║ Blocked: TypeScript, Go, npm, Python (non-Salt) ║"
echo "╚════════════════════════════════════════════════════════════╝"