Repository navigation
fix(graph): a grammar that fails to load costs its language, not the CLI - #804
Ercaner1988 wants to merge 2 commits into
Conversation
extract.ts imported all nine depth-tier grammars at the top of the module. They are native (node-gyp) modules, so any one of them failing to load took the whole CLI down at import time, before argv was read — `--version` and `--help` died exactly like a build, with a node-gyp-build stack trace that never says "graft". On the machine in trailhq#323 that grammar is tree-sitter-kotlin, which ships no prebuilds at all and so cannot load without a C toolchain. Load them through createRequire instead and keep what loads. The extension table is filtered by that, which is all the rest of the pipeline needs: an unavailable language stops claiming its files, and they take the paths a language graft has no grammar for takes today — the breadth tier where a generic row claims the extension, unindexed where none does. Warned once per language, the first time a file it would have claimed comes past, so a repo with no Kotlin in it stays quiet and a Kotlin repo never indexes short in silence. That alone keeps the CLI alive but leaves a language with no breadth-tier row indexing nothing — and Kotlin had none, so the machine in trailhq#323 would start and index no Kotlin at all. tree-sitter-wasm ships a kotlin grammar and queries/kotlin.scm has been in the tree unused since Kotlin was promoted to the depth tier, so the fallback is one registry row. A row whose extension the depth tier also claims is unreachable while the native grammar loads — `.java` has been exactly that all along, which is why the "must not collide" note needed rewriting rather than an exception. Both warmGenericGrammars calls now apply the tier precedence their own parse loops apply, so a fallback row cannot warm a WASM grammar the run will never call. That was already true of `.java`; a second row would have doubled it. Measured on a three-file repo (.ts/.kt/.rs) with tree-sitter-kotlin broken: the CLI does not start before this change; with the isolation alone, 6 nodes [rust, typescript]; with the fallback row too, 11 nodes [kotlin, rust, typescript]. With the grammar healthy the graph is identical either way. The test breaks a grammar both ways a module can be reached, `import` and `require`, so it still fails if the static imports ever come back. Refs trailhq#323 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🌱 graft blast radius1 area changed → 9 areas can be affected. 39 dependent symbols, depth 2. flowchart TB
A0(("Dependency Graph<br/>18 symbols"))
A1(("CLI Entry Point<br/>6 symbols"))
A2(("Pull Request Review<br/>5 symbols"))
A3(("MCP Tools<br/>3 symbols"))
A4(("Language Detection<br/>2 symbols"))
AX(("4 smaller areas<br/>5 symbols"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1,A2,A3,A4 reached;
classDef tail fill:#EEF2F3,stroke:#9AA4A9,stroke-width:1px,color:#3A4247;
class AX tail;
Who knows this code — 6 people across 10 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 39 dependent symbols, grouped by areaDependency Graph — 18 symbols in 9 files
CLI Entry Point — 6 symbols in 3 files
Pull Request Review — 5 symbols in 4 files
MCP Tools — 3 symbols in 1 file
Language Detection — 2 symbols in 1 file
Ignored Code Detection — 2 symbols in 1 file
Context Building — 1 symbol in 1 file
Edit Overlap Detection — 1 symbol in 1 file
Cloud Onboarding — 1 symbol in 1 file
Test signal per changed area — 1 ⚠Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
61 test suites also reference this code76 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
extract.ts imported all nine depth-tier grammars at the top of the module. They are native (node-gyp) modules, so any one of them failing to load took the whole CLI down at import time, before argv was read —
--versionand--helpdied exactly like a build, with a node-gyp-build stack trace that never says "graft". On the machine in #323 that grammar is tree-sitter-kotlin, which ships no prebuilds at all and so cannot load without a C toolchain.Load them through createRequire instead and keep what loads. The extension table is filtered by that, which is all the rest of the pipeline needs: an unavailable language stops claiming its files, and they take the paths a language graft has no grammar for takes today — the breadth tier where a generic row claims the extension, unindexed where none does.
Warned once per language, the first time a file it would have claimed comes past, so a repo with no Kotlin in it stays quiet and a Kotlin repo never indexes short in silence.
That alone keeps the CLI alive but leaves a language with no breadth-tier row indexing nothing — and Kotlin had none, so the machine in #323 would start and index no Kotlin at all. tree-sitter-wasm ships a kotlin grammar and queries/kotlin.scm has been in the tree unused since Kotlin was promoted to the depth tier, so the fallback is one registry row. A row whose extension the depth tier also claims is unreachable while the native grammar loads —
.javahas been exactly that all along, which is why the "must not collide" note needed rewriting rather than an exception.Both warmGenericGrammars calls now apply the tier precedence their own parse loops apply, so a fallback row cannot warm a WASM grammar the run will never call. That was already true of
.java; a second row would have doubled it.Measured on a three-file repo (.ts/.kt/.rs) with tree-sitter-kotlin broken: the CLI does not start before this change; with the isolation alone, 6 nodes [rust, typescript]; with the fallback row too, 11 nodes [kotlin, rust, typescript]. With the grammar healthy the graph is identical either way.
The test breaks a grammar both ways a module can be reached,
importandrequire, so it still fails if the static imports ever come back.Refs #323