|
1 | 1 | # SPDX-License-Identifier: PMPL-1.0-or-later |
2 | | -name: RSR Language Policy |
| 2 | +# RSR Anti-Pattern CI Check |
| 3 | +# SPDX-License-Identifier: PMPL-1.0-or-later |
| 4 | +# |
| 5 | +# Enforces: No TypeScript, No Go, No Python (except SaltStack), No npm |
| 6 | +# Allows: ReScript, Deno, WASM, Rust, OCaml, Haskell, Guile/Scheme |
| 7 | + |
| 8 | +name: RSR Anti-Pattern Check |
3 | 9 |
|
4 | 10 | on: |
5 | 11 | push: |
6 | | - branches: [main] |
| 12 | + branches: [main, master, develop] |
7 | 13 | pull_request: |
8 | | - branches: [main] |
| 14 | + branches: [main, master, develop] |
| 15 | + |
9 | 16 |
|
10 | 17 | permissions: |
11 | 18 | contents: read |
12 | 19 |
|
13 | 20 | jobs: |
14 | | - check-banned-patterns: |
15 | | - name: Check for banned languages/patterns |
| 21 | + antipattern-check: |
16 | 22 | runs-on: ubuntu-latest |
| 23 | + permissions: |
| 24 | + contents: read |
17 | 25 | steps: |
18 | | - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 |
19 | | - |
| 26 | + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
| 27 | + |
20 | 28 | - name: Check for TypeScript |
21 | 29 | run: | |
22 | 30 | python3 << 'PYEOF' |
@@ -130,112 +138,57 @@ jobs: |
130 | 138 | print(f"✅ No TypeScript files outside allowlist ({len(exemption_patterns)} per-repo exemption(s) parsed).") |
131 | 139 | PYEOF |
132 | 140 |
|
133 | | - # Universal builtin allowlist — bridges that need no per-repo declaration. |
134 | | - # Files matching any of these patterns are always allowed. |
135 | | - BUILTIN_GLOBS = [ |
136 | | - '*.d.ts', |
137 | | - '**/bindings/**', |
138 | | - '**/tests/**', '**/test/**', |
139 | | - '**/scripts/**', |
140 | | - '**/mcp-adapter/**', |
141 | | - '**/*vscode*/**', |
142 | | - '**/cli/**', |
143 | | - '**/mod.ts', |
144 | | - '**/lsp-server.ts', '**/lsp_server.ts', '**/lsp.ts', '**/*-lsp.ts', |
145 | | - '**/deno-*/**', |
146 | | - '**/node_modules/**', |
147 | | - '**/vendor/**', |
148 | | - '**/examples/**', |
149 | | - '**/ffi/**', |
150 | | - ] |
151 | | -
|
152 | | - # Per-repo exemptions parsed from .claude/CLAUDE.md "TypeScript Exemptions" table. |
153 | | - # Single source of truth — adding a row here unblocks CI for that path. |
154 | | - # Format expected: |
155 | | - # ### TypeScript Exemptions ... |
156 | | - # | Path | Files | Rationale | Unblock condition | |
157 | | - # |---|---|---|---| |
158 | | - # | `path/to/file.ts` | 1 | ... | ... | |
159 | | - # | `dir/*.ts` | 6 | ... | ... | |
160 | | - exemptions = [] |
161 | | - claude_md = pathlib.Path('.claude/CLAUDE.md') |
162 | | - if claude_md.exists(): |
163 | | - in_table = False |
164 | | - for line in claude_md.read_text(encoding='utf-8').splitlines(): |
165 | | - if re.search(r'TypeScript [Ee]xemptions', line): |
166 | | - in_table = True |
167 | | - continue |
168 | | - if in_table and line.startswith(('### ', '## ', '# ')): |
169 | | - break |
170 | | - if in_table and line.startswith('|'): |
171 | | - m = re.match(r'\|\s*`([^`]+)`', line) |
172 | | - if m: |
173 | | - exemptions.append(m.group(1)) |
174 | | -
|
175 | | - # Find all .ts and .tsx files |
176 | | - found = [] |
177 | | - for ext in ('ts', 'tsx'): |
178 | | - found.extend(str(p) for p in pathlib.Path('.').rglob(f'*.{ext}')) |
179 | | -
|
180 | | - def allowed(path): |
181 | | - p = path.lstrip('./') |
182 | | - for g in BUILTIN_GLOBS + exemptions: |
183 | | - if fnmatch.fnmatchcase(p, g): |
184 | | - return True |
185 | | - # also treat glob ending with / as a directory prefix |
186 | | - base = g.rstrip('/').rstrip('*').rstrip('/') |
187 | | - if base and (p == base or p.startswith(base + '/')): |
188 | | - return True |
189 | | - return False |
190 | | -
|
191 | | - bad = sorted(f for f in found if not allowed(f)) |
192 | | - if bad: |
193 | | - print("❌ TypeScript files detected outside the allowlist.\n") |
194 | | - for f in bad: |
195 | | - print(f" {f}") |
196 | | - print() |
197 | | - print("To resolve, either:") |
198 | | - print(" (a) migrate the file to AffineScript") |
199 | | - print(" (see Human_Programming_Guide.adoc migration chapter), OR") |
200 | | - print(" (b) move it to an allowlisted bridge path") |
201 | | - print(" (bindings/, tests/, scripts/, mcp-adapter/, *vscode*/, cli/, deno-*/, etc.), OR") |
202 | | - print(" (c) add an entry to the 'TypeScript Exemptions' table in .claude/CLAUDE.md") |
203 | | - print(" with rationale + unblock condition.") |
204 | | - if exemptions: |
205 | | - print(f"\n(Currently {len(exemptions)} exemption(s) parsed from .claude/CLAUDE.md.)") |
206 | | - sys.exit(1) |
207 | | - print(f"✅ No TypeScript files outside allowlist ({len(exemptions)} per-repo exemption(s) parsed).") |
208 | | - PYEOF |
209 | | - |
210 | 141 | - name: Check for Go |
211 | 142 | run: | |
212 | 143 | if find . -name "*.go" | grep -q .; then |
213 | | - echo "::error::Go files found! Use Rust instead (per RSR policy)" |
| 144 | + echo "❌ Go files detected - use Rust/WASM instead" |
| 145 | + find . -name "*.go" |
214 | 146 | exit 1 |
215 | 147 | fi |
216 | | - echo "✓ No Go files found" |
217 | | - |
218 | | - - name: Check for npm/Node artifacts |
| 148 | + echo "✅ No Go files" |
| 149 | +
|
| 150 | + - name: Check for Python (non-SaltStack) |
219 | 151 | run: | |
220 | | - if [ -f "package-lock.json" ] || [ -d "node_modules" ]; then |
221 | | - echo "::error::npm artifacts found! Use Deno instead (per RSR policy)" |
| 152 | + PY_FILES=$(find . -name "*.py" | grep -v salt | grep -v _states | grep -v _modules | grep -v pillar | grep -v venv | grep -v __pycache__ || true) |
| 153 | + if [ -n "$PY_FILES" ]; then |
| 154 | + echo "❌ Python files detected - only allowed for SaltStack" |
| 155 | + echo "$PY_FILES" |
222 | 156 | exit 1 |
223 | 157 | fi |
224 | | - echo "✓ No npm artifacts found" |
225 | | - |
226 | | - - name: Check for Python (non-SaltStack) |
| 158 | + echo "✅ No non-SaltStack Python files" |
| 159 | +
|
| 160 | + - name: Check for npm lockfiles |
227 | 161 | run: | |
228 | | - banned_py=$(find . -name "*.py" | grep -v -E "(salt|pillar|states|_modules|_states)" | head -1) |
229 | | - if [ -n "$banned_py" ]; then |
230 | | - echo "::error::Python files found outside SaltStack! Use ReScript/Rust (per RSR policy)" |
| 162 | + if [ -f "package-lock.json" ] || [ -f "yarn.lock" ]; then |
| 163 | + echo "❌ npm/yarn lockfile detected - use Deno instead" |
231 | 164 | exit 1 |
232 | 165 | fi |
233 | | - echo "✓ No banned Python files found" |
234 | | - |
235 | | - - name: Check for Makefiles |
| 166 | + echo "✅ No npm lockfiles" |
| 167 | +
|
| 168 | + - name: Check for tsconfig |
236 | 169 | run: | |
237 | | - if [ -f "Makefile" ] || [ -f "makefile" ] || find . -name "*.mk" | grep -q .; then |
238 | | - echo "::error::Makefile found! Use Justfile or Mustfile instead (per RSR policy)" |
| 170 | + if [ -f "tsconfig.json" ]; then |
| 171 | + echo "❌ tsconfig.json detected - use ReScript instead" |
239 | 172 | exit 1 |
240 | 173 | fi |
241 | | - echo "✓ No Makefiles found" |
| 174 | + echo "✅ No tsconfig.json" |
| 175 | +
|
| 176 | + - name: Verify Deno presence (if package.json exists) |
| 177 | + run: | |
| 178 | + if [ -f "package.json" ]; then |
| 179 | + if [ ! -f "deno.json" ] && [ ! -f "deno.jsonc" ]; then |
| 180 | + echo "⚠️ Warning: package.json without deno.json - migration recommended" |
| 181 | + fi |
| 182 | + fi |
| 183 | + echo "✅ Deno configuration check complete" |
| 184 | +
|
| 185 | + - name: Summary |
| 186 | + run: | |
| 187 | + echo "╔════════════════════════════════════════════════════════════╗" |
| 188 | + echo "║ RSR Anti-Pattern Check Passed ✅ ║" |
| 189 | + echo "║ ║" |
| 190 | + echo "║ Allowed: ReScript, Deno, WASM, Rust, OCaml, Haskell, ║" |
| 191 | + echo "║ Guile/Scheme, SaltStack (Python) ║" |
| 192 | + echo "║ ║" |
| 193 | + echo "║ Blocked: TypeScript, Go, npm, Python (non-Salt) ║" |
| 194 | + echo "╚════════════════════════════════════════════════════════════╝" |
0 commit comments