feat(ci): monitor tree-sitter-c too (report-only, ABI-pinned)

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.)
This commit is contained in:
Gergo Magyar 2026-06-09 16:05:38 +00:00
parent 7023b63605
commit 5ad473d517
3 changed files with 39 additions and 13 deletions

View file

@ -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;

View file

@ -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.');
}

View file

@ -23,7 +23,7 @@ const MOD = pathToFileURL(
let mod: {
readAbi: (root: string) => number | null;
COMPATIBLE_ABI: Set<number>;
GRAMMARS: Record<string, { name: string; npm?: string; github?: string }>;
GRAMMARS: Record<string, { name: string; npm?: string; github?: string; hold?: string }>;
};
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();
}
});
});