Skip to content

Commit 9e2c7f8

Browse files
committed
fix(ci): repair corrupted rsr-antipattern.yml on this branch
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.
1 parent 64ccba8 commit 9e2c7f8

1 file changed

Lines changed: 117 additions & 10 deletions

File tree

.github/workflows/rsr-antipattern.yml

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
# Enforces: No TypeScript, No Go, No Python (except SaltStack), No npm
66
# Allows: ReScript, Deno, WASM, Rust, OCaml, Haskell, Guile/Scheme
77

8-
permissions:
9-
contents: read
10-
118
name: RSR Anti-Pattern Check
129

1310
on:
@@ -16,20 +13,130 @@ on:
1613
pull_request:
1714
branches: [main, master, develop]
1815

16+
17+
permissions:
18+
contents: read
19+
1920
jobs:
2021
antipattern-check:
2122
runs-on: ubuntu-latest
23+
permissions:
24+
contents: read
2225
steps:
23-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
26+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2427

2528
- name: Check for TypeScript
2629
run: |
27-
if find . -name "*.ts" -o -name "*.tsx" | grep -v node_modules | grep -q .; then
28-
echo "❌ TypeScript files detected - use ReScript instead"
29-
find . -name "*.ts" -o -name "*.tsx" | grep -v node_modules
30-
exit 1
31-
fi
32-
echo "✅ No TypeScript files"
30+
python3 << 'PYEOF'
31+
import re, sys, pathlib
32+
33+
# Universal allowlist — bridges and conventions that need no per-repo declaration.
34+
# Implemented as explicit string predicates rather than glob patterns so that
35+
# top-level directories (e.g. tests/foo.ts) are matched the same as nested ones,
36+
# which fnmatch's * cannot do reliably.
37+
DIR_NAMES_ALLOWED = {
38+
'bindings', 'tests', 'test', 'scripts',
39+
'mcp-adapter', 'cli', 'vendor', 'examples', 'ffi',
40+
'node_modules', 'benchmarks',
41+
}
42+
43+
def builtin_allowed(p):
44+
# `p` is a posix-style path with no leading ./
45+
# 1. Type declaration files
46+
if p.endswith('.d.ts'):
47+
return True
48+
# 2. Canonical Deno entrypoint filenames
49+
base = p.rsplit('/', 1)[-1]
50+
if base == 'mod.ts':
51+
return True
52+
# 3. LSP server files (filename suffixes)
53+
if base in ('lsp-server.ts', 'lsp_server.ts', 'lsp.ts') or base.endswith('-lsp.ts'):
54+
return True
55+
# 4. Benchmark files (filename suffixes)
56+
if base.endswith('.bench.ts') or base.endswith('_bench.ts'):
57+
return True
58+
# 5. Any directory segment (excluding basename) matches an allowed dir
59+
segs = p.split('/')
60+
for s in segs[:-1]:
61+
if s in DIR_NAMES_ALLOWED:
62+
return True
63+
# vscode-anything or anything-vscode
64+
if 'vscode' in s:
65+
return True
66+
# deno-named subprojects
67+
if s.startswith('deno-'):
68+
return True
69+
return False
70+
71+
# Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table.
72+
# This is the documented single source of truth: adding one row here unblocks CI.
73+
# Glob characters: '*' and '**' both mean "any chars including /". This loose
74+
# interpretation matches user intent when an exemption row reads, e.g.,
75+
# `affinescript-deno-test/*.ts` (covering nested files too).
76+
def glob_to_regex(g):
77+
out = []
78+
for c in g.lstrip('./'):
79+
if c == '*': out.append('.*')
80+
elif c == '?': out.append('.')
81+
elif c in '.+(){}[]|^$\\': out.append(re.escape(c))
82+
else: out.append(c)
83+
return re.compile('^' + ''.join(out) + '$')
84+
85+
exemption_patterns = []
86+
claude_md = pathlib.Path('.claude/CLAUDE.md')
87+
if claude_md.exists():
88+
in_table = False
89+
for line in claude_md.read_text(encoding='utf-8').splitlines():
90+
if re.search(r'TypeScript [Ee]xemptions', line):
91+
in_table = True
92+
continue
93+
if in_table and line.startswith(('### ', '## ', '# ')):
94+
break
95+
if in_table and line.startswith('|'):
96+
m = re.match(r'\|\s*`([^`]+)`', line)
97+
if m:
98+
exemption_patterns.append((m.group(1), glob_to_regex(m.group(1))))
99+
100+
def exempt(p):
101+
for raw, regex in exemption_patterns:
102+
if regex.match(p):
103+
return True
104+
# Also allow exact-path matches and prefix matches for paths
105+
# ending in `/`
106+
if p == raw.lstrip('./'):
107+
return True
108+
if raw.endswith('/') and p.startswith(raw.lstrip('./')):
109+
return True
110+
return False
111+
112+
# Find all .ts and .tsx files (excluding common dot-dirs that find normally skips)
113+
found = []
114+
for ext in ('ts', 'tsx'):
115+
for p in pathlib.Path('.').rglob(f'*.{ext}'):
116+
parts = p.parts
117+
if any(part.startswith('.') and part not in ('.', '..') for part in parts):
118+
continue
119+
found.append(p.as_posix().lstrip('./'))
120+
121+
bad = sorted(f for f in found if not (builtin_allowed(f) or exempt(f)))
122+
if bad:
123+
print("❌ TypeScript files detected outside the allowlist.\n")
124+
for f in bad:
125+
print(f" {f}")
126+
print()
127+
print("To resolve, choose one:")
128+
print(" (a) migrate the file to AffineScript")
129+
print(" (see Human_Programming_Guide.adoc 'Migrating from -script Languages')")
130+
print(" (b) move to an allowlisted bridge path")
131+
print(" (bindings/, tests/, test/, scripts/, benchmarks/, mcp-adapter/,")
132+
print(" *vscode*/, cli/, deno-*/, vendor/, examples/, ffi/)")
133+
print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md")
134+
print(" with rationale + unblock condition")
135+
if exemption_patterns:
136+
print(f"\n(Currently {len(exemption_patterns)} exemption(s) parsed from .claude/CLAUDE.md.)")
137+
sys.exit(1)
138+
print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).")
139+
PYEOF
33140
34141
- name: Check for Go
35142
run: |

0 commit comments

Comments
 (0)