diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 1db40f2c..91bfe5f2 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -85,8 +85,7 @@ for the canonical statement. | Language/Tool | Use Case | Notes | |---------------|----------|-------| | **AffineScript** | Primary application code | Compiles to typed-wasm; affine/linear types. Replaces ReScript across the estate (RS/TS/JS → AffineScript → typed-wasm). | -| **Bun** | JS/TS runtime & package management (tier 1) | Default for all new work. Executes `.ts` directly, no build step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. | -| **Deno** | JS/TS runtime (tier 2) | Grandfathered. Existing Deno projects need not migrate; prefer over pnpm/npm where Bun cannot be used. | +| **Bun** | JS runtime & package management (tier 1) | Default for all new work. Runs compiled ESM/JS directly — no bundler step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. | | **Rust/SPARK** | Performance-critical, systems, WASM, CLI tools, safety-critical | "Rust" always means "Rust/SPARK" per terminology note above. Preferred over Ada where reachable. | | **Zig** | **APIs, FFIs, gateways, client SDKs (estate default 2026-05-28)**, memory-safe systems where Rust/SPARK is overkill | Zig is the estate-wide default for all API/FFI/gateway/client-SDK work unless explicitly special-cased; Idris2 owns ABIs. Completed V-lang→Zig migration 2026-05-28. | | **Idris2** | Formal verification (primary, ABI-style proofs) | ATS2 rejected. Proven-library status in `proven` repo. | @@ -120,18 +119,43 @@ for the canonical statement. > > The distinction that keeps both documents coherent: **Bun is the runtime, tier 1 > and unchanged; AffineScript is the language for new application code.** Those -> were run together in the withdrawn text. TypeScript is permitted only where -> AffineScript cannot reach — the same narrow, transitional carve-out JavaScript -> holds below. +> were run together in the withdrawn text. +> +> ⚠ **TIGHTENED 2026-08-27 — owner ruling.** Asked about the Bun row advertising direct +> `.ts` execution, the owner ruled: *"no typescript … that should not exist at all."* +> The previous sentence here read "TypeScript is permitted only where AffineScript cannot +> reach". That is now **too permissive**: TypeScript is not a fallback tier, and no tool +> description in this file may advertise TypeScript support. Every `.ts` reference has been +> removed from the Bun row, including "JS/TS" in its label. +> +> ⚠ **This collides with the "TypeScript Exemptions (Approved)" table below**, which +> documents real technical carve-outs — `.d.ts` declaration files, the VS Code extension +> host (npm/Node-native, `@vscode/test-electron` has no alternative), and MCP/LSP protocol +> glue. Those are **not** stylistic preferences and cannot simply be deleted. They are left +> standing and flagged for an explicit owner decision: either retire each carve-out with a +> migration path, or restate the rule as "no new TypeScript, these listed exemptions +> excepted". **Not resolved unilaterally.** > > ReScript's ban is uncontested, and its migration destination is **AffineScript**. +> **SUPERSEDED 2026-08-26 — Deno is no longer tier 2.** The owner ruled: +> *"deno is to go and bun is the way we are going, put it first everywhere unless not +> possible and explain why if not."* The "Bun > Deno > pnpm > npm" ordering above described +> a **preference**; it is now a **removal**. Deno has been **moved out of ALLOWED into BANNED** accordingly - a struck-through row in an ALLOWED table is ambiguous to the agents that read this file (codacy raised exactly that on #655). +> +> This matters because this file is what agents read first. While it said Deno was +> "grandfathered … need not migrate", agents correctly declined to migrate — and the +> dependency rules below compounded it: *"No package.json for runtime deps — use deno.json +> imports"* left repos with **no manifest at all**. `hyperpolymath/ubicity` could not build +> under any toolchain for exactly this reason (see ubicity#107). Both rules are corrected. + ### BANNED - Do Not Use | Banned | Replacement | Notes | |--------|-------------|-------| | TypeScript | AffineScript | RS/TS/JS → AffineScript → typed-wasm. | | **ReScript** | AffineScript | Banned in new code as of 2026-04-30. Existing `.res` files migrate to `.affine` directly (do not pass through ReScript). | +| **Deno** | Bun | **Being removed.** Owner ruling 2026-08-26: *"deno is to go and bun is the way we are going, put it first everywhere unless not possible and explain why if not."* Existing Deno projects must migrate to Bun; where Bun genuinely cannot be used, the reason must be documented in the repo. Assessment of all 30 remaining `deno.json` locations: #658. | | Node.js | Bun | Bun is Node-compatible; run the code, drop the runtime. | | npm | Bun | npm is tier 4 — *permitted, never preferred*, not banned. `package-lock.json` must still not be tracked (standards#67). | | yarn | Bun | yarn is not in the tier list at all. | @@ -179,13 +203,13 @@ Both are FOSS with independent governance (no Big Tech). (`docs/migrations/js-to-affinescript`) carves out MCP/LSP protocol glue and VSCode-host code (*"MCP glue … Should NOT appear in `portable now`"*). Those stay until the AffineScript MCP/LSP/VSCode bindings ship (affinescript#446). - Genuinely-portable Deno CLI scripts are the convert-now bucket. + Genuinely-portable Deno CLI scripts are the convert-now bucket; anything not yet portable to AffineScript moves to **Bun**, not left on Deno. - **Compile-verify, wire-first.** A port is not done until the `.affine` builds green (`just check`) and the compiled output is wired as the live entry with the original removed *in the same PR*. Never ship an unbuilt `.affine` or delete a working `.ts`/`.res` for one that has not compiled. -2. **No package.json for runtime deps** - Use deno.json imports -3. **No node_modules in production** - Deno caches deps automatically +2. **Use `package.json` + `bun.lock` for JS runtime deps** - Bun is npm-compatible; a manifest is REQUIRED. (This line previously said "No package.json - use deno.json imports", which left repos with undeclared dependencies that could not build under any toolchain.) +3. **`bun install --production` for production deps** - Bun resolves from `package.json` and pins via `bun.lock` 4. **No Go code** - Use Rust instead 5. **No Python** - All Python must be rewritten 6. **No Kotlin/Swift for mobile** - Use Tauri 2.0+ or Dioxus @@ -195,7 +219,7 @@ Both are FOSS with independent governance (no Big Tech). - **Primary**: Guix (guix.scm) - **Fallback**: Nix (flake.nix) -- **JS deps**: Deno (deno.json imports) +- **JS deps**: **Bun** (`package.json` + `bun.lock`); `bunx ` to run one-off tooling ### Documentation Format diff --git a/tools/policy/check-language-policy.sh b/tools/policy/check-language-policy.sh new file mode 100755 index 00000000..a495cc96 --- /dev/null +++ b/tools/policy/check-language-policy.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Language-policy drift gate. +# +# WHY THIS EXISTS. The estate's language policy is duplicated into ~372 per-repo +# `.claude/CLAUDE.md` files across 131 repos. On 2026-08-26 a census found 868 of them +# still listed **Bun as BANNED** with Deno as its replacement - the exact inverse of the +# standing ruling - and nothing had ever detected it. Correcting `standards` fixes one copy; +# agents read the local one. +# +# WHY ASSERTIONS, NOT A DIFF. The copies are legitimately not identical: repos carry their +# own exemption tables, architecture notes and carve-outs. A byte-for-byte generator would +# be permanently red. So this gate asserts the INVARIANTS the policy must satisfy, whatever +# the surrounding wording. +# +# Exit 0 = compliant. Exit 1 = drift. Every failure prints file:line. +set -uo pipefail +status=0 +files=$(git ls-files '*CLAUDE.md' 2>/dev/null | grep -v node_modules) +[ -z "$files" ] && { echo "no CLAUDE.md tracked - nothing to check"; exit 0; } + +fail(){ printf ' \033[31mFAIL\033[0m %s\n %s\n' "$1" "$2"; status=1; } + +for f in $files; do + echo "checking $f" + + # --- must NOT appear ------------------------------------------------------- + # 1. Bun banned. This is the inversion that went undetected across 868 files. + if grep -nF -- '| Bun | Deno |' "$f" >/dev/null; then + fail "$f:$(grep -nF -- '| Bun | Deno |' "$f" | head -1 | cut -d: -f1)" \ + 'Bun is listed as BANNED with Deno as replacement - inverted. Bun is tier 1.' + fi + # 2. The rule that told repos not to declare dependencies at all. hyperpolymath/ubicity + # a phrase inside a blockquote or quotation marks is HISTORY, not policy + live(){ grep -vE '^[[:space:]]*>' "$1" | grep -vE '"[^"]*'"$2"'[^"]*"|\u201c[^\u201d]*'"$2"'[^\u201d]*\u201d'; } + # imported zod and glob, shipped no manifest, and could not build under ANY toolchain. + if live "$f" 'No package.json for runtime deps' | grep -qF 'No package.json for runtime deps'; then + fail "$f:$(grep -nF 'No package.json for runtime deps' "$f" | head -1 | cut -d: -f1)" \ + 'Forbids declaring dependencies. Bun is npm-compatible; a manifest is REQUIRED.' + fi + if live "$f" 'deno.json imports' | grep -qF 'deno.json imports'; then + fail "$f:$(grep -nF 'deno.json imports' "$f" | head -1 | cut -d: -f1)" \ + 'Directs dependency declaration into deno.json. Use package.json + bun.lock.' + fi + # 3. No tool description may advertise TypeScript. Owner ruling 2026-08-27: + # "no typescript ... that should not exist at all." + if grep -nE 'Executes .\.ts. directly|JS/TS runtime' "$f" >/dev/null; then + fail "$f:$(grep -nE 'Executes .\.ts. directly|JS/TS runtime' "$f" | head -1 | cut -d: -f1)" \ + 'Advertises TypeScript execution. TypeScript is banned; do not describe tools as TS runtimes.' + fi + # 4. Blanking scars. A bulk purge substituted a token with an EMPTY STRING, which also + # produced `rm -rf /lib` in wordpress-tools (the lethal shape is /path -> /path). + if awk -F'|' 'NF==4 && $2 ~ /^[[:space:]]*$/{exit 0} END{exit 1}' "$f"; then + fail "$f" 'Policy table row with an EMPTY first cell - blanking scar from a bulk substitution.' + fi + if grep -nF '| **** |' "$f" >/dev/null; then + fail "$f:$(grep -nF '| **** |' "$f" | head -1 | cut -d: -f1)" \ + 'Empty bold cell (****) - the language name was blanked out.' + fi + if grep -nE '\*\*No new +files\*\*|Only where +cannot' "$f" >/dev/null; then + fail "$f" 'Enforcement rule with a blanked language name.' + fi + # 5. A rule may not ban the language it mandates. + if grep -nE '^\| AffineScript \| AffineScript \|' "$f" >/dev/null; then + fail "$f" 'BANNED table maps AffineScript to itself - it bans the mandated language.' + fi + + # --- must appear, if the file carries a language-policy table --------------- + if grep -qE '^### (ALLOWED|BANNED)' "$f"; then + { grep -qE '^\|[[:space:]]*\*\*Bun\*\*[[:space:]]*\|' "$f" || grep -qiE '^[-*][[:space:]]+\*{0,2}Bun\*{0,2}\b' "$f"; } || \ + fail "$f" 'No Bun row in ALLOWED. Bun is the tier-1 JS runtime and package manager.' + { grep -qE '^\|[[:space:]]*\*{0,2}Deno\*{0,2}[[:space:]]*\|[[:space:]]*\*{0,2}Bun\*{0,2}[[:space:]]*\|' "$f" || grep -qiE '^[-*][[:space:]]+Deno[[:space:]]*\(use Bun\)' "$f"; } || \ + fail "$f" 'Deno is not listed in BANNED with Bun as its replacement (ruling 2026-08-26).' + fi +done + +if [ $status -eq 0 ]; then echo "language policy OK"; else + echo + echo "Language-policy drift detected. Canonical source: hyperpolymath/standards .claude/CLAUDE.md" + echo "Fix the local copy; do not weaken this gate." +fi +exit $status