From 85a66e554f9bd8222c1338d6d66358084fc1e6cd Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 12:58:52 +0100 Subject: [PATCH 1/2] fix(ci): rsr-antipattern.yml duplicate heredoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **rsr-antipattern.yml**: deletes the orphan duplicate Python script (lines 274-428) that escapes the heredoc and exits 127. Mirrors hyperpolymath/rsr-template-repo#39. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/rsr-antipattern.yml | 531 ++++++++++++++++---------- 1 file changed, 323 insertions(+), 208 deletions(-) diff --git a/.github/workflows/rsr-antipattern.yml b/.github/workflows/rsr-antipattern.yml index 5902bb5..be56af3 100644 --- a/.github/workflows/rsr-antipattern.yml +++ b/.github/workflows/rsr-antipattern.yml @@ -1,270 +1,385 @@ # SPDX-License-Identifier: PMPL-1.0-or-later + # RSR Anti-Pattern CI Check +# SPDX-License-Id +entifier: 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 +# Enforces: No +TypeScript, No Go, No Python (except SaltStac +k), No npm +# Allows: ReScript, Deno, WASM, Ru +st, OCaml, Haskell, Guile/Scheme -name: RSR Anti-Pattern Check +name: RSR A +nti-Pattern Check on: push: - branches: [main, master, develop] + branches: +[main, master, develop] pull_request: - branches: [main, master, develop] + b +ranches: [main, master, develop] -permissions: +permission +s: contents: read jobs: - antipattern-check: + antipattern-chec +k: runs-on: ubuntu-latest - permissions: + permissions +: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - use +s: actions/checkout@de0fac2e4500dabe0009e6721 +4ff5f5447ce83dd # v6.0.2 - - name: Check for TypeScript + - name: Check + for TypeScript run: | - python3 << 'PYEOF' - import re, sys, pathlib + pyth +on3 << 'PYEOF' + import re, sys, path +lib - # Universal allowlist — bridges and conventions that need no per-repo declaration. - # Implemented as explicit string predicates rather than glob patterns so that - # top-level directories (e.g. tests/foo.ts) are matched the same as nested ones, - # which fnmatch's * cannot do reliably. + # Universal allowlist — brid +ges and conventions that need no per-repo dec +laration. + # Implemented as explicit + string predicates rather than glob patterns +so that + # top-level directories (e. +g. tests/foo.ts) are matched the same as nest +ed ones, + # which fnmatch's * cannot + do reliably. DIR_NAMES_ALLOWED = { - 'bindings', 'tests', 'test', 'scripts', - 'mcp-adapter', 'cli', 'vendor', 'examples', 'ffi', - 'node_modules', 'benchmarks', + + 'bindings', 'tests', 'test', ' +scripts', + 'mcp-adapter', 'cli', + 'vendor', 'examples', 'ffi', + ' +node_modules', 'benchmarks', } - def builtin_allowed(p): - # `p` is a posix-style path with no leading ./ + + def builtin_allowed(p): + +# `p` is a posix-style path with no leading . +/ # 1. Type declaration files - if p.endswith('.d.ts'): - return True - # 2. Canonical Deno entrypoint filenames - base = p.rsplit('/', 1)[-1] - if base == 'mod.ts': - return True - # 3. LSP server files (filename suffixes) - if base in ('lsp-server.ts', 'lsp_server.ts', 'lsp.ts') or base.endswith('-lsp.ts'): - return True - # 4. Benchmark files (filename suffixes) - if base.endswith('.bench.ts') or base.endswith('_bench.ts'): + + if p.endswith('.d.ts'): + + return True + # 2. Cano +nical Deno entrypoint filenames + + base = p.rsplit('/', 1)[-1] + if + base == 'mod.ts': + return T +rue + # 3. LSP server files (file +name suffixes) + if base in ('lsp +-server.ts', 'lsp_server.ts', 'lsp.ts') or ba +se.endswith('-lsp.ts'): + ret +urn True + # 4. Benchmark files ( +filename suffixes) + if base.ends +with('.bench.ts') or base.endswith('_bench.ts +'): return True - # 5. Any directory segment (excluding basename) matches an allowed dir - segs = p.split('/') - for s in segs[:-1]: - if s in DIR_NAMES_ALLOWED: + + # 5. Any directory segment (excluding base +name) matches an allowed dir + se +gs = p.split('/') + for s in segs +[:-1]: + if s in DIR_NAMES_AL +LOWED: return True - # vscode-anything or anything-vscode + + # vscode-anything or anything-v +scode if 'vscode' in s: - return True - # deno-named subprojects - if s.startswith('deno-'): - return True + + return True + + # deno-named subprojects + + if s.startswith('deno-'): + + return True return False - # Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table. - # This is the documented single source of truth: adding one row here unblocks CI. - # Glob characters: '*' and '**' both mean "any chars including /". This loose - # interpretation matches user intent when an exemption row reads, e.g., - # `affinescript-deno-test/*.ts` (covering nested files too). - def glob_to_regex(g): + + # Per-repo exemptions parsed from . +claude/CLAUDE.md "TypeScript Exemptions" tabl +e. + # This is the documented single +source of truth: adding one row here unblocks + CI. + # Glob characters: '*' and '** +' both mean "any chars including /". This loo +se + # interpretation matches user in +tent when an exemption row reads, e.g., + + # `affinescript-deno-test/*.ts` (coverin +g nested files too). + def glob_to_re +gex(g): out = [] - for c in g.lstrip('./'): - if c == '*': out.append('.*') - elif c == '?': out.append('.') - elif c in '.+(){}[]|^$\\': out.append(re.escape(c)) - else: out.append(c) - return re.compile('^' + ''.join(out) + '$') + +for c in g.lstrip('./'): + if + c == '*': out.append('.*') + + elif c == '?': out.append('.') + + elif c in '.+(){}[]|^$\\': out.append(re +.escape(c)) + else: out.appen +d(c) + return re.compile('^' + '' +.join(out) + '$') - exemption_patterns = [] - claude_md = pathlib.Path('.claude/CLAUDE.md') - if claude_md.exists(): + exemption_patter +ns = [] + claude_md = pathlib.Path('. +claude/CLAUDE.md') + if claude_md.exi +sts(): in_table = False - for line in claude_md.read_text(encoding='utf-8').splitlines(): - if re.search(r'TypeScript [Ee]xemptions', line): + + for line in claude_md.read_text(encodi +ng='utf-8').splitlines(): + i +f re.search(r'TypeScript [Ee]xemptions', line +): in_table = True - continue - if in_table and line.startswith(('### ', '## ', '# ')): + + continue + +if in_table and line.startswith(('### ', '## +', '# ')): break - if in_table and line.startswith('|'): - m = re.match(r'\|\s*`([^`]+)`', line) - if m: - exemption_patterns.append((m.group(1), glob_to_regex(m.group(1)))) + + if in_table and line.startswith(' +|'): + m = re.match(r'\|\ +s*`([^`]+)`', line) + if +m: + exemption_patter +ns.append((m.group(1), glob_to_regex(m.group( +1)))) def exempt(p): - for raw, regex in exemption_patterns: - if regex.match(p): - return True - # Also allow exact-path matches and prefix matches for paths + + for raw, regex in exemption_patterns: + + if regex.match(p): + + return True + # Also +allow exact-path matches and prefix matches f +or paths # ending in `/` - if p == raw.lstrip('./'): - return True - if raw.endswith('/') and p.startswith(raw.lstrip('./')): - return True + + if p == raw.lstrip('./'): + + return True + + if raw.endswith('/') and p.startswith(raw +.lstrip('./')): + return +True return False - # Find all .ts and .tsx files (excluding common dot-dirs that find normally skips) - found = [] - for ext in ('ts', 'tsx'): - for p in pathlib.Path('.').rglob(f'*.{ext}'): - parts = p.parts - if any(part.startswith('.') and part not in ('.', '..') for part in parts): + # +Find all .ts and .tsx files (excluding common + dot-dirs that find normally skips) + + found = [] + for ext in ('ts', 'tsx' +): + for p in pathlib.Path('.').r +glob(f'*.{ext}'): + parts = p +.parts + if any(part.startswi +th('.') and part not in ('.', '..') for part +in parts): continue - found.append(p.as_posix().lstrip('./')) + + found.append(p.as_posix().lstr +ip('./')) - bad = sorted(f for f in found if not (builtin_allowed(f) or exempt(f))) + bad = sorted(f for f in +found if not (builtin_allowed(f) or exempt(f) +)) if bad: - print("❌ TypeScript files detected outside the allowlist.\n") + print("❌ + TypeScript files detected outside the allowl +ist.\n") for f in bad: - print(f" {f}") - print() - print("To resolve, choose one:") - print(" (a) migrate the file to AffineScript") - print(" (see Human_Programming_Guide.adoc 'Migrating from -script Languages')") - print(" (b) move to an allowlisted bridge path") - print(" (bindings/, tests/, test/, scripts/, benchmarks/, mcp-adapter/,") - print(" *vscode*/, cli/, deno-*/, vendor/, examples/, ffi/)") - print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md") - print(" with rationale + unblock condition") - if exemption_patterns: - print(f"\n(Currently {len(exemption_patterns)} exemption(s) parsed from .claude/CLAUDE.md.)") - sys.exit(1) - print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).") - PYEOF - - # 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}')) + + print(f" {f}") + print +() + print("To resolve, choose on +e:") + print(" (a) migrate the f +ile to AffineScript") + print(" + (see Human_Programming_Guide.adoc 'Migrat +ing from -script Languages')") + +print(" (b) move to an allowlisted bridge pa +th") + print(" (bindings/, t +ests/, test/, scripts/, benchmarks/, mcp-adap +ter/,") + print(" *vscode*/ +, cli/, deno-*/, vendor/, examples/, ffi/)") - 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).") + print(" (c) add an entry to th +e 'TypeScript Exemptions' table in .claude/CL +AUDE.md") + print(" with rat +ionale + unblock condition") + if + exemption_patterns: + print( +f"\n(Currently {len(exemption_patterns)} exem +ption(s) parsed from .claude/CLAUDE.md.)") + + sys.exit(1) + print(f"✅ + No TypeScript files outside allowlist ({len( +exemption_patterns)} per-repo exemption(s) pa +rsed).") PYEOF - name: Check for Go - run: | - if find . -name "*.go" | grep -q .; then - echo "❌ Go files detected - use Rust/WASM instead" - find . -name "*.go" + r +un: | + if find . -name "*.go" | grep + -q .; then + echo "❌ Go files de +tected - use Rust/WASM instead" + f +ind . -name "*.go" exit 1 - fi + + 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" + + - name: Check for Python (non-SaltStack) + + run: | + PY_FILES=$(find . -name + "*.py" | grep -v salt | grep -v _states | gr +ep -v _modules | grep -v pillar | grep -v ven +v | grep -v __pycache__ || true) + if + [ -n "$PY_FILES" ]; then + echo "� +�� Python files detected - only allowed for S +altStack" echo "$PY_FILES" - exit 1 + + exit 1 fi - echo "✅ No non-SaltStack Python files" + echo "✅ + No non-SaltStack Python files" - - name: Check for npm lockfiles + - 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 + + if [ -f "package-lock.json" ] || [ -f " +yarn.lock" ]; then + echo "❌ npm/ +yarn lockfile detected - use Deno instead" + + exit 1 fi - echo "✅ No npm lockfiles" + echo +"✅ No npm lockfiles" - - name: Check for tsconfig + - name: Check f +or tsconfig run: | - if [ -f "tsconfig.json" ]; then - echo "❌ tsconfig.json detected - use ReScript instead" + if [ -f +"tsconfig.json" ]; then + echo "❌ + tsconfig.json detected - use ReScript instea +d" exit 1 fi - echo "✅ No tsconfig.json" + +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 + - name: Ve +rify 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 d +eno.json - migration recommended" + + fi fi - echo "✅ Deno configuration check complete" + echo "✅ Deno con +figuration check complete" - - name: Summary + - name: Sum +mary 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 "╚════════════════════════════════════════════════════════════╝" + echo "╔══ +═══════════════ +═══════════════ +═══════════════ +═════════════╗" + + echo "║ RSR Anti-Pattern + Check Passed ✅ ║" + + echo "║ + ║" + ec +ho "║ Allowed: ReScript, Deno, WASM, Rust, + OCaml, Haskell, ║" + echo "║ + Guile/Scheme, SaltStack (Python) + ║" + echo "║ + + ║" + echo "║ Blocked: T +ypeScript, Go, npm, Python (non-Salt) + ║" + echo "╚══════� +��══════════════� +��══════════════� +��══════════════� +��════════╝" + + From 098454e21a4298ee96e2c0ab184b7c5bed6c2eea Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 14 May 2026 14:05:33 +0100 Subject: [PATCH 2/2] fix(ci): repair corrupted rsr-antipattern.yml on this branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repairs the corrupted `rsr-antipattern.yml` from the prior commit on this branch. See hyperpolymath/stapeln#34 for the full root-cause writeup — the original sweep built its canonical via `gh api --jq '.content'` piped through PowerShell, but `gh` line-wraps base64 in the terminal pipe, so each chunk was decoded separately and the rejoined file has mid-word line breaks (`# SPDX-License-Id\nentifier:`, `name: RSR A\nnti-Pattern`, etc.). GitHub Actions can't parse the resulting YAML — the workflow completes in 0 seconds with no jobs. This commit overwrites the file with the correct content, built via raw byte download (`Accept: application/vnd.github.raw`) and `[System.IO.File]::WriteAllBytes` so no pipe ever touches the bytes. Round-trip byte-verified against canonical. --- .github/workflows/rsr-antipattern.yml | 459 ++++++++------------------ 1 file changed, 134 insertions(+), 325 deletions(-) diff --git a/.github/workflows/rsr-antipattern.yml b/.github/workflows/rsr-antipattern.yml index be56af3..ff16d3a 100644 --- a/.github/workflows/rsr-antipattern.yml +++ b/.github/workflows/rsr-antipattern.yml @@ -1,385 +1,194 @@ # SPDX-License-Identifier: PMPL-1.0-or-later - # RSR Anti-Pattern CI Check -# SPDX-License-Id -entifier: PMPL-1.0-or-later +# SPDX-License-Identifier: PMPL-1.0-or-later # -# Enforces: No -TypeScript, No Go, No Python (except SaltStac -k), No npm -# Allows: ReScript, Deno, WASM, Ru -st, OCaml, Haskell, Guile/Scheme +# Enforces: No TypeScript, No Go, No Python (except SaltStack), No npm +# Allows: ReScript, Deno, WASM, Rust, OCaml, Haskell, Guile/Scheme -name: RSR A -nti-Pattern Check +name: RSR Anti-Pattern Check on: push: - branches: -[main, master, develop] + branches: [main, master, develop] pull_request: - b -ranches: [main, master, develop] + branches: [main, master, develop] -permission -s: +permissions: contents: read jobs: - antipattern-chec -k: + antipattern-check: runs-on: ubuntu-latest - permissions -: + permissions: contents: read steps: - - use -s: actions/checkout@de0fac2e4500dabe0009e6721 -4ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check - for TypeScript + - name: Check for TypeScript run: | - pyth -on3 << 'PYEOF' - import re, sys, path -lib + python3 << 'PYEOF' + import re, sys, pathlib - # Universal allowlist — brid -ges and conventions that need no per-repo dec -laration. - # Implemented as explicit - string predicates rather than glob patterns -so that - # top-level directories (e. -g. tests/foo.ts) are matched the same as nest -ed ones, - # which fnmatch's * cannot - do reliably. + # Universal allowlist — bridges and conventions that need no per-repo declaration. + # Implemented as explicit string predicates rather than glob patterns so that + # top-level directories (e.g. tests/foo.ts) are matched the same as nested ones, + # which fnmatch's * cannot do reliably. DIR_NAMES_ALLOWED = { - - 'bindings', 'tests', 'test', ' -scripts', - 'mcp-adapter', 'cli', - 'vendor', 'examples', 'ffi', - ' -node_modules', 'benchmarks', + 'bindings', 'tests', 'test', 'scripts', + 'mcp-adapter', 'cli', 'vendor', 'examples', 'ffi', + 'node_modules', 'benchmarks', } - - def builtin_allowed(p): - -# `p` is a posix-style path with no leading . -/ + def builtin_allowed(p): + # `p` is a posix-style path with no leading ./ # 1. Type declaration files - - if p.endswith('.d.ts'): - - return True - # 2. Cano -nical Deno entrypoint filenames - - base = p.rsplit('/', 1)[-1] - if - base == 'mod.ts': - return T -rue - # 3. LSP server files (file -name suffixes) - if base in ('lsp --server.ts', 'lsp_server.ts', 'lsp.ts') or ba -se.endswith('-lsp.ts'): - ret -urn True - # 4. Benchmark files ( -filename suffixes) - if base.ends -with('.bench.ts') or base.endswith('_bench.ts -'): + if p.endswith('.d.ts'): + return True + # 2. Canonical Deno entrypoint filenames + base = p.rsplit('/', 1)[-1] + if base == 'mod.ts': + return True + # 3. LSP server files (filename suffixes) + if base in ('lsp-server.ts', 'lsp_server.ts', 'lsp.ts') or base.endswith('-lsp.ts'): + return True + # 4. Benchmark files (filename suffixes) + if base.endswith('.bench.ts') or base.endswith('_bench.ts'): return True - - # 5. Any directory segment (excluding base -name) matches an allowed dir - se -gs = p.split('/') - for s in segs -[:-1]: - if s in DIR_NAMES_AL -LOWED: + # 5. Any directory segment (excluding basename) matches an allowed dir + segs = p.split('/') + for s in segs[:-1]: + if s in DIR_NAMES_ALLOWED: return True - - # vscode-anything or anything-v -scode + # vscode-anything or anything-vscode if 'vscode' in s: - - return True - - # deno-named subprojects - - if s.startswith('deno-'): - - return True + return True + # deno-named subprojects + if s.startswith('deno-'): + return True return False - - # Per-repo exemptions parsed from . -claude/CLAUDE.md "TypeScript Exemptions" tabl -e. - # This is the documented single -source of truth: adding one row here unblocks - CI. - # Glob characters: '*' and '** -' both mean "any chars including /". This loo -se - # interpretation matches user in -tent when an exemption row reads, e.g., - - # `affinescript-deno-test/*.ts` (coverin -g nested files too). - def glob_to_re -gex(g): + # Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table. + # This is the documented single source of truth: adding one row here unblocks CI. + # Glob characters: '*' and '**' both mean "any chars including /". This loose + # interpretation matches user intent when an exemption row reads, e.g., + # `affinescript-deno-test/*.ts` (covering nested files too). + def glob_to_regex(g): out = [] - -for c in g.lstrip('./'): - if - c == '*': out.append('.*') - - elif c == '?': out.append('.') - - elif c in '.+(){}[]|^$\\': out.append(re -.escape(c)) - else: out.appen -d(c) - return re.compile('^' + '' -.join(out) + '$') - - exemption_patter -ns = [] - claude_md = pathlib.Path('. -claude/CLAUDE.md') - if claude_md.exi -sts(): + for c in g.lstrip('./'): + if c == '*': out.append('.*') + elif c == '?': out.append('.') + elif c in '.+(){}[]|^$\\': out.append(re.escape(c)) + else: out.append(c) + return re.compile('^' + ''.join(out) + '$') + + exemption_patterns = [] + claude_md = pathlib.Path('.claude/CLAUDE.md') + if claude_md.exists(): in_table = False - - for line in claude_md.read_text(encodi -ng='utf-8').splitlines(): - i -f re.search(r'TypeScript [Ee]xemptions', line -): + 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(('### ', '## -', '# ')): + continue + if in_table and line.startswith(('### ', '## ', '# ')): break - - if in_table and line.startswith(' -|'): - m = re.match(r'\|\ -s*`([^`]+)`', line) - if -m: - exemption_patter -ns.append((m.group(1), glob_to_regex(m.group( -1)))) + if in_table and line.startswith('|'): + m = re.match(r'\|\s*`([^`]+)`', line) + if m: + exemption_patterns.append((m.group(1), glob_to_regex(m.group(1)))) def exempt(p): - - for raw, regex in exemption_patterns: - - if regex.match(p): - - return True - # Also -allow exact-path matches and prefix matches f -or paths + for raw, regex in exemption_patterns: + if regex.match(p): + return True + # Also allow exact-path matches and prefix matches for paths # ending in `/` - - if p == raw.lstrip('./'): - - return True - - if raw.endswith('/') and p.startswith(raw -.lstrip('./')): - return -True + if p == raw.lstrip('./'): + return True + if raw.endswith('/') and p.startswith(raw.lstrip('./')): + return True return False - # -Find all .ts and .tsx files (excluding common - dot-dirs that find normally skips) - - found = [] - for ext in ('ts', 'tsx' -): - for p in pathlib.Path('.').r -glob(f'*.{ext}'): - parts = p -.parts - if any(part.startswi -th('.') and part not in ('.', '..') for part -in parts): + # Find all .ts and .tsx files (excluding common dot-dirs that find normally skips) + found = [] + for ext in ('ts', 'tsx'): + for p in pathlib.Path('.').rglob(f'*.{ext}'): + parts = p.parts + if any(part.startswith('.') and part not in ('.', '..') for part in parts): continue - - found.append(p.as_posix().lstr -ip('./')) + found.append(p.as_posix().lstrip('./')) - bad = sorted(f for f in -found if not (builtin_allowed(f) or exempt(f) -)) + bad = sorted(f for f in found if not (builtin_allowed(f) or exempt(f))) if bad: - print("❌ - TypeScript files detected outside the allowl -ist.\n") + print("❌ TypeScript files detected outside the allowlist.\n") for f in bad: - - print(f" {f}") - print -() - print("To resolve, choose on -e:") - print(" (a) migrate the f -ile to AffineScript") - print(" - (see Human_Programming_Guide.adoc 'Migrat -ing from -script Languages')") - -print(" (b) move to an allowlisted bridge pa -th") - print(" (bindings/, t -ests/, test/, scripts/, benchmarks/, mcp-adap -ter/,") - print(" *vscode*/ -, cli/, deno-*/, vendor/, examples/, ffi/)") - - print(" (c) add an entry to th -e 'TypeScript Exemptions' table in .claude/CL -AUDE.md") - print(" with rat -ionale + unblock condition") - if - exemption_patterns: - print( -f"\n(Currently {len(exemption_patterns)} exem -ption(s) parsed from .claude/CLAUDE.md.)") - - sys.exit(1) - print(f"✅ - No TypeScript files outside allowlist ({len( -exemption_patterns)} per-repo exemption(s) pa -rsed).") + print(f" {f}") + print() + print("To resolve, choose one:") + print(" (a) migrate the file to AffineScript") + print(" (see Human_Programming_Guide.adoc 'Migrating from -script Languages')") + print(" (b) move to an allowlisted bridge path") + print(" (bindings/, tests/, test/, scripts/, benchmarks/, mcp-adapter/,") + print(" *vscode*/, cli/, deno-*/, vendor/, examples/, ffi/)") + print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md") + print(" with rationale + unblock condition") + if exemption_patterns: + print(f"\n(Currently {len(exemption_patterns)} exemption(s) parsed from .claude/CLAUDE.md.)") + sys.exit(1) + print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).") PYEOF - name: Check for Go - r -un: | - if find . -name "*.go" | grep - -q .; then - echo "❌ Go files de -tected - use Rust/WASM instead" - f -ind . -name "*.go" + run: | + if find . -name "*.go" | grep -q .; then + echo "❌ Go files detected - use Rust/WASM instead" + find . -name "*.go" exit 1 - - fi + fi echo "✅ No Go files" - - - name: Check for Python (non-SaltStack) - - run: | - PY_FILES=$(find . -name - "*.py" | grep -v salt | grep -v _states | gr -ep -v _modules | grep -v pillar | grep -v ven -v | grep -v __pycache__ || true) - if - [ -n "$PY_FILES" ]; then - echo "� -�� Python files detected - only allowed for S -altStack" + - 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 + exit 1 fi - echo "✅ - No non-SaltStack Python files" + echo "✅ No non-SaltStack Python files" - - name -: Check for npm lockfiles + - 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 + if [ -f "package-lock.json" ] || [ -f "yarn.lock" ]; then + echo "❌ npm/yarn lockfile detected - use Deno instead" + exit 1 fi - echo -"✅ No npm lockfiles" + echo "✅ No npm lockfiles" - - name: Check f -or tsconfig + - name: Check for tsconfig run: | - if [ -f -"tsconfig.json" ]; then - echo "❌ - tsconfig.json detected - use ReScript instea -d" + if [ -f "tsconfig.json" ]; then + echo "❌ tsconfig.json detected - use ReScript instead" exit 1 fi - -echo "✅ No tsconfig.json" + echo "✅ No tsconfig.json" - - name: Ve -rify 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 d -eno.json - migration recommended" - - fi + - 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 con -figuration check complete" + echo "✅ Deno configuration check complete" - - name: Sum -mary + - name: Summary run: | - echo "╔══ -═══════════════ -═══════════════ -═══════════════ -═════════════╗" - - echo "║ RSR Anti-Pattern - Check Passed ✅ ║" - - echo "║ - ║" - ec -ho "║ Allowed: ReScript, Deno, WASM, Rust, - OCaml, Haskell, ║" - echo "║ - Guile/Scheme, SaltStack (Python) - ║" - echo "║ - - ║" - echo "║ Blocked: T -ypeScript, Go, npm, Python (non-Salt) - ║" - echo "╚══════� -��══════════════� -��══════════════� -��══════════════� -��════════╝" - - + 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 "╚════════════════════════════════════════════════════════════╝"