From 5ad473d5177db7384c32557bb3d5cb9ecf49860c Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 9 Jun 2026 16:05:38 +0000 Subject: [PATCH] feat(ci): monitor tree-sitter-c too (report-only, ABI-pinned) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit c was excluded from the update monitor, so an upstream c update went unnoticed. Include it, but as report-only via a `hold`: c is ABI-pinned at 0.21.4 (#1242/#858) and must not auto-bump without a tree-sitter runtime upgrade, so an available c update is detected + surfaced (notice + job summary) but never auto-PR'd — even if it were ABI-13/14. `--apply c` refuses defensively. (Live: upstream c is 0.24.1 / ABI 15 today, so c is doubly held — reported, not applied.) --- .github/scripts/update-vendored-grammars.mjs | 27 +++++++++++++++---- .github/workflows/grammar-update-monitor.yml | 10 ++++--- .../test/unit/grammar-update-monitor.test.ts | 15 ++++++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/scripts/update-vendored-grammars.mjs b/.github/scripts/update-vendored-grammars.mjs index 2b0151713..aa2fffa74 100644 --- a/.github/scripts/update-vendored-grammars.mjs +++ b/.github/scripts/update-vendored-grammars.mjs @@ -21,8 +21,10 @@ * node update-vendored-grammars.mjs # detect only → JSON report on stdout * node update-vendored-grammars.mjs --apply X # re-vendor grammar X in place * - * tree-sitter-c is intentionally absent: it is HELD at 0.21.4 for ABI safety - * (#1242) and must never be auto-bumped. + * tree-sitter-c is MONITORED but report-only (`hold`): it is ABI-pinned at 0.21.4 + * (#1242/#858) and must not auto-bump without a tree-sitter runtime upgrade, so an + * available c update is detected + reported but never auto-applied — even if it is + * ABI-13/14. A maintainer re-vendors it deliberately. */ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; @@ -37,8 +39,15 @@ const VENDOR = path.join(REPO_ROOT, 'gitnexus', 'vendor'); const COMPATIBLE_ABI = new Set([13, 14]); // tree-sitter@0.21.1 LANGUAGE_VERSION range // Source-of-origin per grammar. npm grammars resolve `latest` via the registry; -// github grammars (no usable npm release) track the default branch HEAD. +// github grammars (no usable npm release) track the default branch HEAD. A `hold` +// reason makes a grammar report-only: updates are detected + surfaced but never +// auto-applied (c is ABI-pinned and must not move without a runtime upgrade). const GRAMMARS = { + c: { + name: 'tree-sitter-c', + npm: 'tree-sitter-c', + hold: 'ABI-pinned at 0.21.4 (#1242/#858) — needs a tree-sitter runtime upgrade before bumping', + }, swift: { name: 'tree-sitter-swift', npm: 'tree-sitter-swift' }, kotlin: { name: 'tree-sitter-kotlin', npm: 'tree-sitter-kotlin' }, dart: { name: 'tree-sitter-dart', github: 'UserNobody14/tree-sitter-dart' }, @@ -147,8 +156,10 @@ function detect() { update: newer, abi, abiCompatible: abi == null ? null : COMPATIBLE_ABI.has(abi), - // Only auto-appliable when there's an update AND the ABI is known-compatible. - applicable: newer && abi != null && COMPATIBLE_ABI.has(abi), + hold: g.hold || null, + // Auto-appliable only when there's an update, the ABI is known-compatible, + // AND the grammar is not on a policy hold (c). + applicable: newer && abi != null && COMPATIBLE_ABI.has(abi) && !g.hold, }); } return report; @@ -177,6 +188,12 @@ function apply(key) { console.error(`unknown grammar '${key}'`); process.exit(2); } + if (g.hold) { + console.error( + `${key}: report-only (${g.hold}); not auto-applied. Re-vendor manually if intended.`, + ); + process.exit(3); + } const have = vendoredVersion(g); const up = resolveUpstream(g); const newer = up.kind === 'npm' ? up.version !== have : !have || up.version !== have; diff --git a/.github/workflows/grammar-update-monitor.yml b/.github/workflows/grammar-update-monitor.yml index a0bb3307f..3628ec27a 100644 --- a/.github/workflows/grammar-update-monitor.yml +++ b/.github/workflows/grammar-update-monitor.yml @@ -9,8 +9,10 @@ name: Vendored grammar update monitor # # ABI-INCOMPATIBLE updates (the common case — upstreams move to newer tree-sitter) # are reported as a notice + job summary, NOT applied, so the monitor never opens -# doomed PRs. tree-sitter-c is excluded entirely: it is HELD at 0.21.4 for ABI -# safety (#1242) and must never be auto-bumped. +# doomed PRs. tree-sitter-c is MONITORED but report-only: it is ABI-pinned at +# 0.21.4 (#1242/#858), so an available c update is surfaced (notice + summary) but +# never auto-bumped — a maintainer re-vendors it deliberately after a runtime +# upgrade. # # Concurrency convention: see CONTRIBUTING.md -> "GitHub Actions — Concurrency Convention". @@ -132,13 +134,13 @@ jobs: // Summary const s = core.summary.addHeading('Vendored grammar update monitor'); if (applied.length) s.addRaw(`\n**Opened PRs:** ${applied.map((a) => `${a.grammar}→${a.upstream} (#${a.pr})`).join(', ')}\n`); - if (held.length) s.addRaw(`\n**Held (ABI-incompatible / unknown — needs the tree-sitter runtime upgrade):** ${held.map((h) => `${h.grammar} ${h.upstream} (ABI ${h.abi ?? '?'})`).join(', ')}\n`); + if (held.length) s.addRaw(`\n**Held (not auto-applied):** ${held.map((h) => `${h.grammar} ${h.upstream} (${h.hold ? 'report-only: ' + h.hold : 'ABI ' + (h.abi ?? '?') + ' — needs the tree-sitter runtime upgrade'})`).join(', ')}\n`); if (skipped.length) s.addRaw(`\n**Skipped:** ${skipped.map((x) => `${x.grammar} (${x.reason})`).join(', ')}\n`); if (errors.length) s.addRaw(`\n**Errors:** ${errors.map((e) => `${e.grammar}: ${e.error}`).join('; ')}\n`); if (!applied.length && !held.length && !skipped.length && !errors.length) s.addRaw('\nAll vendored grammars are up to date. ✅\n'); await s.write(); - for (const h of held) core.notice(`${h.grammar}: update to ${h.upstream} available but ABI ${h.abi ?? 'unknown'} (need 13/14) — held until the tree-sitter runtime upgrade.`); + for (const h of held) core.notice(`${h.grammar}: update to ${h.upstream} available — ${h.hold ? `report-only (${h.hold})` : `ABI ${h.abi ?? 'unknown'} (need 13/14), held until the tree-sitter runtime upgrade`}.`); if (!hasApp && (applied.length || skipped.some((x) => /secret/.test(x.reason)))) { core.notice('RELEASE_APP_ID / RELEASE_APP_PRIVATE_KEY not configured — update PRs were not opened. Provision the App to enable auto-PRs.'); } diff --git a/gitnexus/test/unit/grammar-update-monitor.test.ts b/gitnexus/test/unit/grammar-update-monitor.test.ts index 4e3944196..43078e02b 100644 --- a/gitnexus/test/unit/grammar-update-monitor.test.ts +++ b/gitnexus/test/unit/grammar-update-monitor.test.ts @@ -23,7 +23,7 @@ const MOD = pathToFileURL( let mod: { readAbi: (root: string) => number | null; COMPATIBLE_ABI: Set; - GRAMMARS: Record; + GRAMMARS: Record; }; let tmp: string; @@ -62,10 +62,17 @@ describe('COMPATIBLE_ABI gate', () => { }); describe('GRAMMARS registry', () => { - it('covers swift/kotlin (npm) + dart/proto (github) and EXCLUDES the ABI-pinned c', () => { - expect(Object.keys(mod.GRAMMARS).sort()).toEqual(['dart', 'kotlin', 'proto', 'swift']); + it('covers all five grammars (swift/kotlin npm, dart/proto github, c npm)', () => { + expect(Object.keys(mod.GRAMMARS).sort()).toEqual(['c', 'dart', 'kotlin', 'proto', 'swift']); expect(mod.GRAMMARS.swift.npm).toBe('tree-sitter-swift'); expect(mod.GRAMMARS.dart.github).toContain('tree-sitter-dart'); - expect(mod.GRAMMARS).not.toHaveProperty('c'); + }); + + it('monitors c but marks it report-only (ABI-pinned hold); the rest are auto-updatable', () => { + expect(mod.GRAMMARS.c.npm).toBe('tree-sitter-c'); + expect(mod.GRAMMARS.c.hold).toBeTruthy(); // detected/reported, never auto-applied + for (const k of ['swift', 'kotlin', 'dart', 'proto']) { + expect(mod.GRAMMARS[k].hold).toBeUndefined(); + } }); });