graph-indexer 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +260 -1
  2. package/README.md +232 -312
  3. package/SECURITY.md +37 -59
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +8 -0
  6. package/integrations/claude-code/.mcp.json +8 -0
  7. package/integrations/claude-code/agents/code-structure.md +22 -0
  8. package/integrations/claude-code/hooks/gi-hook.mjs +99 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +124 -0
  11. package/package.json +31 -105
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +356 -0
  14. package/src/cli/init.mjs +255 -0
  15. package/src/cli/resident.mjs +175 -0
  16. package/src/index/discover.mjs +130 -0
  17. package/src/index/fingerprint.mjs +30 -0
  18. package/src/index/graph.mjs +61 -0
  19. package/src/index/indexer.mjs +476 -0
  20. package/src/index/modules.mjs +331 -0
  21. package/src/index/resolver.mjs +954 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +973 -0
  25. package/src/parse/arity.mjs +152 -0
  26. package/src/parse/extract.mjs +1031 -0
  27. package/src/parse/lang/csharp.mjs +106 -0
  28. package/src/parse/lang/ecma.mjs +438 -0
  29. package/src/parse/lang/go.mjs +194 -0
  30. package/src/parse/lang/jvm.mjs +118 -0
  31. package/src/parse/lang/kotlin.mjs +97 -0
  32. package/src/parse/lang/misc.mjs +115 -0
  33. package/src/parse/lang/native.mjs +126 -0
  34. package/src/parse/lang/python.mjs +153 -0
  35. package/src/parse/lang/rust.mjs +157 -0
  36. package/src/parse/lang/scripting.mjs +219 -0
  37. package/src/parse/languages.mjs +48 -0
  38. package/src/parse/runtime.mjs +56 -0
  39. package/src/query/check.mjs +343 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +680 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +119 -0
  44. package/src/query/textsearch.mjs +160 -0
  45. package/src/search/search.mjs +210 -0
  46. package/src/search/tokenize.mjs +188 -0
  47. package/src/store/db.mjs +169 -0
  48. package/src/util/paths.mjs +24 -0
  49. package/vendor/grammars/bash.wasm.gz +0 -0
  50. package/vendor/grammars/c.wasm.gz +0 -0
  51. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  52. package/vendor/grammars/cpp.wasm.gz +0 -0
  53. package/vendor/grammars/css.wasm.gz +0 -0
  54. package/vendor/grammars/go.wasm.gz +0 -0
  55. package/vendor/grammars/java.wasm.gz +0 -0
  56. package/vendor/grammars/javascript.wasm.gz +0 -0
  57. package/vendor/grammars/kotlin.wasm.gz +0 -0
  58. package/vendor/grammars/manifest.json +148 -0
  59. package/vendor/grammars/php.wasm.gz +0 -0
  60. package/vendor/grammars/python.wasm.gz +0 -0
  61. package/vendor/grammars/ruby.wasm.gz +0 -0
  62. package/vendor/grammars/rust.wasm.gz +0 -0
  63. package/vendor/grammars/scala.wasm.gz +0 -0
  64. package/vendor/grammars/tsx.wasm.gz +0 -0
  65. package/vendor/grammars/typescript.wasm.gz +0 -0
  66. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  67. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  68. package/cli-ui.mjs +0 -78
  69. package/colbert.mjs +0 -168
  70. package/config.mjs +0 -494
  71. package/daemon-ctl.mjs +0 -175
  72. package/daemon-lock.mjs +0 -69
  73. package/embedders/python/mlx_embed_server.py +0 -61
  74. package/embedders/python/requirements-mlx.txt +0 -5
  75. package/embedders/setup-mlx.mjs +0 -110
  76. package/embeddings.mjs +0 -571
  77. package/engine/binary.mjs +0 -437
  78. package/engine/memory.mjs +0 -1003
  79. package/engine/sqlite.mjs +0 -1136
  80. package/enrichment.mjs +0 -521
  81. package/git-signals.mjs +0 -248
  82. package/indexer.mjs +0 -546
  83. package/init.mjs +0 -1809
  84. package/layout.mjs +0 -123
  85. package/mcp/centrality.mjs +0 -117
  86. package/mcp/format.mjs +0 -61
  87. package/mcp/resolver.mjs +0 -116
  88. package/mcp/symbolgraph.mjs +0 -137
  89. package/mcp/taint.mjs +0 -225
  90. package/mcp/tools.mjs +0 -1582
  91. package/mcp/topology.mjs +0 -676
  92. package/mcp-server.mjs +0 -197
  93. package/parse/extractor.mjs +0 -628
  94. package/parse/imports.mjs +0 -365
  95. package/parse/interprocedural.mjs +0 -134
  96. package/parse/languages.mjs +0 -253
  97. package/parse/metadata.mjs +0 -701
  98. package/parse/routes.mjs +0 -327
  99. package/parse/scip.mjs +0 -391
  100. package/parse/taint-patterns.mjs +0 -119
  101. package/prompts/CORE.md +0 -91
  102. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  103. package/prompts/INTEGRATION.md +0 -123
  104. package/prompts/frameworks/ANDROID.md +0 -17
  105. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  106. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  107. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  108. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  109. package/prompts/frameworks/RAILS.md +0 -20
  110. package/prompts/frameworks/REACT.md +0 -20
  111. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  112. package/prompts/languages/BASH.md +0 -22
  113. package/prompts/languages/C.md +0 -21
  114. package/prompts/languages/CSHARP.md +0 -24
  115. package/prompts/languages/CSS_SCSS.md +0 -22
  116. package/prompts/languages/GO.md +0 -24
  117. package/prompts/languages/JAVA.md +0 -23
  118. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  119. package/prompts/languages/KOTLIN.md +0 -24
  120. package/prompts/languages/PHP.md +0 -23
  121. package/prompts/languages/PYTHON.md +0 -23
  122. package/prompts/languages/RUBY.md +0 -23
  123. package/prompts/languages/RUST.md +0 -21
  124. package/prompts/languages/SWIFT.md +0 -21
  125. package/seal.mjs +0 -400
  126. package/search-core.mjs +0 -777
  127. package/search-sparse.mjs +0 -183
  128. package/storage.mjs +0 -73
  129. package/watch-daemon.mjs +0 -289
@@ -0,0 +1,219 @@
1
+ /** Ruby, PHP and Bash extraction specs. */
2
+
3
+ // ─── Ruby ──────────────────────────────────────────────────────────────────────
4
+ const RUBY_CALLBACKS = 'before_action|after_action|around_action|skip_before_action|prepend_before_action|before_filter|after_filter|validate|validates_with|before_save|after_save|around_save|after_commit|after_create_commit|after_update_commit|before_validation|after_validation|before_create|after_create|before_update|after_update|before_destroy|after_destroy|helper_method|delegate|alias_method';
5
+ const RUBY_QUERY = `
6
+ [(block) (do_block) (lambda)] @scope
7
+ (method name: (_) @name) @def.method
8
+ (singleton_method name: (_) @name) @def.method
9
+ (class name: [(constant) @name (scope_resolution name: (constant) @name)]) @def.class
10
+ (module name: [(constant) @name (scope_resolution name: (constant) @name)]) @def.module
11
+ (assignment left: (constant) @name) @def.constant
12
+
13
+ (call method: (identifier) @name) @ref.call
14
+ (call receiver: (_) @recv method: (identifier) @name) @ref.call
15
+ (call receiver: [(constant) @name (scope_resolution name: (constant) @name)] method: (identifier) @_new (#eq? @_new "new")) @ref.new
16
+ (superclass [(constant) @name (scope_resolution name: (constant) @name)]) @ref.inherit
17
+ (call method: (identifier) @_inc arguments: (argument_list [(constant) @name (scope_resolution name: (constant) @name)]) (#match? @_inc "^(include|extend|prepend)$")) @ref.inherit
18
+ (call method: (identifier) @_cb arguments: (argument_list (simple_symbol) @name) (#match? @_cb "^(${RUBY_CALLBACKS})$")) @ref.value
19
+ (scope_resolution scope: (constant) @recv name: (constant) @name) @ref.type
20
+ (constant) @name @ref.type
21
+
22
+ (call method: (identifier) @_r arguments: (argument_list . (string) @src) (#match? @_r "^(require|require_relative|load|autoload)$")) @import.require
23
+
24
+ (assignment left: (identifier) @bind.name right: (call receiver: (constant) @bind.new method: (identifier) @_n (#eq? @_n "new"))) @bind
25
+ (assignment left: (instance_variable) @field.name right: (call receiver: (constant) @field.new method: (identifier) @_n2 (#eq? @_n2 "new"))) @field
26
+ `;
27
+
28
+ function rubyRequire(callNode, srcNode) {
29
+ const method = callNode.childForFieldName('method')?.text;
30
+ const source = srcNode.text.replace(/^['"]|['"]$/g, '');
31
+ return [{ source: method === 'require_relative' ? (source.startsWith('.') ? source : './' + source) : source, imported: '*', local: null }];
32
+ }
33
+
34
+ export const ruby = {
35
+ id: 'ruby',
36
+ grammar: 'ruby',
37
+ extensions: ['.rb', '.rake', '.gemspec'],
38
+ family: 'ruby',
39
+ query: RUBY_QUERY,
40
+ implicitThis: true,
41
+ selfNames: ['self'],
42
+ externalReceivers: new Set(['Rails', 'ActiveRecord', 'Time', 'Date', 'DateTime', 'File', 'Dir', 'JSON', 'YAML',
43
+ 'Kernel', 'Math', 'Logger', 'ENV', 'STDOUT', 'STDERR', 'Struct', 'Integer', 'String', 'Array', 'Hash', 'Set',
44
+ 'Float', 'Regexp', 'Thread', 'Process', 'IO', 'URI', 'Net', 'OpenStruct', 'SecureRandom', 'Digest', 'Base64']),
45
+ builtinCalls: new Set(['puts', 'print', 'p', 'pp', 'raise', 'require', 'require_relative', 'include', 'extend',
46
+ 'attr_reader', 'attr_writer', 'attr_accessor', 'private', 'protected', 'public', 'lambda', 'proc', 'loop',
47
+ 'block_given?', 'yield', 'new', 'freeze', 'dup', 'to_s', 'to_i', 'to_a', 'to_h', 'to_sym', 'nil?', 'present?',
48
+ 'blank?', 'respond_to?', 'is_a?', 'kind_of?', 'send', 'public_send', 'tap', 'then', 'each', 'map', 'select',
49
+ 'reject', 'find', 'where', 'let', 'describe', 'it', 'context', 'expect', 'subject', 'before', 'after']),
50
+ testFile: /(^|\/)(spec|test)\/|_spec\.rb$|_test\.rb$/,
51
+ commentTypes: ['comment'],
52
+ parseImport: () => [],
53
+ parseRequire: rubyRequire,
54
+ normalizeRefName: (name) => name.replace(/^:/, ''),
55
+ refineKind(kind, d, parent) {
56
+ if (kind === 'method' && !parent) return 'function';
57
+ return kind;
58
+ },
59
+ isExported: () => true,
60
+ visibility: null,
61
+ receiverDescriptor: (node) => (node.type === 'instance_variable' ? 'this.' + node.text.slice(1) : undefined),
62
+ isPrimitiveType: () => false,
63
+ bodyField: 'body',
64
+ };
65
+
66
+ // ─── PHP ───────────────────────────────────────────────────────────────────────
67
+ const PT = (cap) => `[(named_type (name) @${cap}) (named_type (qualified_name (name) @${cap} .)) (optional_type (named_type (name) @${cap}))]`;
68
+ const PHP_QUERY = `
69
+ [(anonymous_function) (arrow_function)] @scope
70
+ (return_statement (_) @ret)
71
+ (function_definition name: (name) @name) @def.function
72
+ (class_declaration name: (name) @name) @def.class
73
+ (interface_declaration name: (name) @name) @def.interface
74
+ (trait_declaration name: (name) @name) @def.trait
75
+ (enum_declaration name: (name) @name) @def.enum
76
+ (method_declaration name: (name) @name return_type: ${PT('type')}) @def.method
77
+ (method_declaration name: (name) @name) @def.method
78
+ (property_declaration type: ${PT('type')} (property_element name: (variable_name (name) @name))) @def.property
79
+ (property_declaration (property_element name: (variable_name (name) @name))) @def.property
80
+ (const_declaration (const_element (name) @name)) @def.constant
81
+ (enum_case name: (name) @name) @def.constant
82
+
83
+ (function_call_expression function: (name) @name) @ref.call
84
+ (function_call_expression function: (qualified_name (name) @name .)) @ref.call
85
+ (member_call_expression object: (_) @recv name: (name) @name) @ref.call
86
+ (nullsafe_member_call_expression object: (_) @recv name: (name) @name) @ref.call
87
+ (scoped_call_expression scope: (_) @recv name: (name) @name) @ref.call
88
+ (object_creation_expression (name) @name) @ref.new
89
+ (object_creation_expression (qualified_name (name) @name .)) @ref.new
90
+ (base_clause (name) @name) @ref.inherit
91
+ (base_clause (qualified_name (name) @name .)) @ref.inherit
92
+ (class_interface_clause (name) @name) @ref.inherit
93
+ (class_interface_clause (qualified_name (name) @name .)) @ref.inherit
94
+ (use_declaration (name) @name) @ref.inherit
95
+ (use_declaration (qualified_name (name) @name .)) @ref.inherit
96
+ (named_type (name) @name) @ref.type
97
+ (named_type (qualified_name (name) @name .)) @ref.type
98
+ (class_constant_access_expression . (name) @name) @ref.type
99
+ (class_constant_access_expression . (qualified_name (name) @name .)) @ref.type
100
+ (attribute (name) @name) @ref.decorator
101
+ (attribute (qualified_name (name) @name .)) @ref.decorator
102
+
103
+ (namespace_use_declaration) @import
104
+
105
+ (simple_parameter type: ${PT('bind.type')} name: (variable_name (name) @bind.name)) @bind
106
+ (assignment_expression left: (variable_name (name) @bind.name) right: (object_creation_expression (name) @bind.new)) @bind
107
+ (assignment_expression left: (variable_name (name) @bind.name) right: (object_creation_expression (qualified_name (name) @bind.new .))) @bind
108
+ (property_promotion_parameter type: ${PT('field.type')} name: (variable_name (name) @field.name)) @field
109
+ (property_declaration type: ${PT('field.type')} (property_element name: (variable_name (name) @field.name))) @field
110
+ (assignment_expression left: (member_access_expression object: (variable_name (name) @_t) name: (name) @field.name) right: (object_creation_expression (name) @field.new) (#eq? @_t "this")) @field
111
+ (assignment_expression left: (member_access_expression object: (variable_name (name) @_t2) name: (name) @field.name) right: (variable_name (name) @field.var) (#eq? @_t2 "this")) @field
112
+ `;
113
+
114
+ function phpUse(node) {
115
+ const out = [];
116
+ for (const clause of node.namedChildren) {
117
+ if (clause.type === 'namespace_use_clause') {
118
+ const nameNode = clause.namedChildren.find(c => c.type === 'qualified_name' || c.type === 'name');
119
+ const full = nameNode?.text.replace(/^\\/, '') ?? '';
120
+ const alias = clause.childForFieldName('alias')?.text;
121
+ const parts = full.split('\\');
122
+ const name = parts.pop();
123
+ out.push({ source: parts.join('\\'), imported: name, local: alias || name });
124
+ } else if (clause.type === 'namespace_use_group') {
125
+ const prefix = node.namedChildren.find(c => c.type === 'namespace_name')?.text ?? '';
126
+ for (const g of clause.namedChildren) {
127
+ const nameNode = g.namedChildren.find(c => c.type === 'qualified_name' || c.type === 'name');
128
+ if (!nameNode) continue;
129
+ const full = (prefix ? prefix + '\\' : '') + nameNode.text;
130
+ const parts = full.split('\\');
131
+ const name = parts.pop();
132
+ out.push({ source: parts.join('\\'), imported: name, local: g.childForFieldName('alias')?.text || name });
133
+ }
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+
139
+ function phpVisibility(node) {
140
+ const v = node.namedChildren.find(c => c.type === 'visibility_modifier');
141
+ return v ? v.text : null;
142
+ }
143
+
144
+ function phpFileInfo(root) {
145
+ const ns = root.namedChildren.find(c => c.type === 'namespace_definition');
146
+ return { package: ns?.childForFieldName('name')?.text ?? null };
147
+ }
148
+
149
+ export const php = {
150
+ id: 'php',
151
+ grammar: 'php',
152
+ extensions: ['.php', '.phtml'],
153
+ family: 'php',
154
+ query: PHP_QUERY,
155
+ implicitThis: false,
156
+ selfNames: ['$this', 'this', 'self', 'static'],
157
+ externalReceivers: new Set(['DB', 'Log', 'Cache', 'Config', 'Route', 'Auth', 'Str', 'Arr', 'Carbon', 'Http', 'Storage',
158
+ 'Session', 'Request', 'Response', 'Validator', 'Hash', 'Event', 'Queue', 'Mail', 'Gate', 'App', 'URL', 'View',
159
+ 'Redirect', 'Artisan', 'Schema', 'Crypt', 'Cookie', 'Lang', 'Notification', 'Password', 'Bus', 'File']),
160
+ builtinCalls: new Set(['array_map', 'array_filter', 'array_merge', 'array_keys', 'array_values', 'in_array', 'count',
161
+ 'isset', 'empty', 'unset', 'sprintf', 'printf', 'implode', 'explode', 'str_replace', 'strlen', 'strtolower',
162
+ 'strtoupper', 'trim', 'json_encode', 'json_decode', 'is_array', 'is_string', 'is_null', 'is_int', 'intval',
163
+ 'array_key_exists', 'var_dump', 'print_r', 'die', 'exit', 'define', 'defined', 'compact', 'extract', 'app', 'config',
164
+ 'env', 'view', 'response', 'redirect', 'route', 'url', 'trans', '__', 'collect', 'now', 'dd', 'dump', 'abort',
165
+ 'request', 'auth', 'session', 'logger', 'info', 'event', 'dispatch', 'optional', 'tap', 'value', 'with']),
166
+ testFile: /(^|\/)(tests?|Tests?)\/|Test\.php$/,
167
+ commentTypes: ['comment'],
168
+ parseImport: phpUse,
169
+ isExported: (node) => phpVisibility(node) !== 'private',
170
+ visibility: phpVisibility,
171
+ receiverDescriptor(node) {
172
+ const t = node.text.replace(/\s+/g, '');
173
+ if (t === '$this') return 'this';
174
+ const norm = t.replace(/^\$/, '').replace(/->|::|\?->/g, '.').replace(/\.\$/g, '.');
175
+ if (/^[A-Za-z_][\w]*(\.[A-Za-z_][\w]*){0,4}$/.test(norm)) return norm.startsWith('this.') ? norm : norm;
176
+ return undefined;
177
+ },
178
+ isPrimitiveType: (t) => /^(int|string|bool|float|array|mixed|void|null|object|callable|iterable|self|static|never|false|true)$/i.test(t),
179
+ decoratorsOf(node) {
180
+ const al = node.childForFieldName('attributes');
181
+ return al ? al.text.replace(/[#[\]]/g, ' ').split(/[\s,]+/).filter(x => /^[A-Z]\w*$/.test(x)).slice(0, 8) : [];
182
+ },
183
+ fileInfo: phpFileInfo,
184
+ bodyField: 'body',
185
+ };
186
+
187
+ // ─── Bash ──────────────────────────────────────────────────────────────────────
188
+ const BASH_QUERY = `
189
+ (function_definition name: (word) @name) @def.function
190
+ (command name: (command_name (word) @name)) @ref.call
191
+ (command name: (command_name (word) @_src) argument: (_) @src (#match? @_src "^(source|\\\\.)$")) @import.require
192
+ `;
193
+
194
+ const BASH_BUILTINS = new Set(['echo', 'cd', 'ls', 'cat', 'rm', 'cp', 'mv', 'mkdir', 'rmdir', 'touch', 'ln', 'export',
195
+ 'local', 'readonly', 'declare', 'unset', 'shift', 'read', 'printf', 'exit', 'return', 'eval', 'exec', 'trap', 'wait',
196
+ 'sleep', 'pwd', 'set', 'shopt', 'source', '.', 'test', '[', '[[', 'true', 'false', 'kill', 'jobs', 'type', 'command',
197
+ 'getopts', 'grep', 'sed', 'awk', 'cut', 'tr', 'sort', 'uniq', 'head', 'tail', 'find', 'xargs', 'chmod', 'chown', 'tee',
198
+ 'wc', 'basename', 'dirname', 'tar', 'curl', 'wget', 'env', 'git', 'npm', 'node', 'make', 'docker', 'python', 'python3',
199
+ 'which', 'hash', 'builtin', 'alias', 'unalias', 'printenv', 'date', 'uname', 'id', 'whoami', 'sudo', 'ps', 'nohup', 'bash', 'sh', 'zsh']);
200
+
201
+ export const bash = {
202
+ id: 'bash',
203
+ grammar: 'bash',
204
+ extensions: ['.sh', '.bash', '.zsh'],
205
+ family: 'shell',
206
+ query: BASH_QUERY,
207
+ implicitThis: false,
208
+ selfNames: [],
209
+ externalReceivers: new Set(),
210
+ builtinCalls: BASH_BUILTINS,
211
+ testFile: /(^|\/)(tests?)\/|_test\.sh$|\.bats$/,
212
+ commentTypes: ['comment'],
213
+ parseImport: () => [],
214
+ parseRequire: (node, src) => [{ source: src.text.replace(/^['"]|['"]$/g, '').replace(/^\$\{?\w+\}?\//, ''), imported: '*', local: null }],
215
+ isExported: () => true,
216
+ visibility: null,
217
+ isPrimitiveType: () => true,
218
+ bodyField: 'body',
219
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Language registry: file extension → extraction spec.
3
+ */
4
+ import path from 'node:path';
5
+ import { javascript, typescript, tsx } from './lang/ecma.mjs';
6
+ import { python } from './lang/python.mjs';
7
+ import { go } from './lang/go.mjs';
8
+ import { java } from './lang/jvm.mjs';
9
+ import { kotlin } from './lang/kotlin.mjs';
10
+ import { csharp } from './lang/csharp.mjs';
11
+ import { rust } from './lang/rust.mjs';
12
+ import { c, cpp } from './lang/native.mjs';
13
+ import { ruby, php, bash } from './lang/scripting.mjs';
14
+ import { scala, css } from './lang/misc.mjs';
15
+
16
+ export const LANGUAGES = [javascript, typescript, tsx, python, go, java, kotlin, scala, csharp, rust, c, cpp, ruby, php, bash, css];
17
+
18
+ const byExt = new Map();
19
+ for (const spec of LANGUAGES) for (const ext of spec.extensions) byExt.set(ext, spec);
20
+
21
+ /** Headers are ambiguous between C and C++; a sibling .cpp/.cc or C++ syntax decides at index time. */
22
+ export function specForPath(p) {
23
+ const base = path.basename(p);
24
+ const ext = path.extname(base).toLowerCase();
25
+ if (base.endsWith('.d.ts')) return typescript;
26
+ return byExt.get(ext) ?? null;
27
+ }
28
+
29
+ export function supportedExtensions() { return [...byExt.keys()]; }
30
+
31
+ export function languageById(id) { return LANGUAGES.find(l => l.id === id) ?? null; }
32
+
33
+ // constructors named by the language rather than declared as such: a method called `initialize` is
34
+ // Ruby's constructor (`Foo.new`) but an ordinary method in TypeScript or Python
35
+ const CONSTRUCTOR_NAMES = {
36
+ javascript: ['constructor'], typescript: ['constructor'], tsx: ['constructor'],
37
+ python: ['__init__', '__new__'], ruby: ['initialize'], php: ['__construct'],
38
+ };
39
+
40
+ /**
41
+ * Is this definition a constructor, invoked through its class (`new Foo()`, `Foo()`, `Foo.new`)?
42
+ * `sym`: { name, kind, lang } — a language id, as the index stores it.
43
+ */
44
+ export function isConstructor(sym) {
45
+ if (!sym) return false;
46
+ if (sym.kind === 'constructor') return true;
47
+ return CONSTRUCTOR_NAMES[sym.lang]?.includes(sym.name) ?? false;
48
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tree-sitter runtime: the WASM build of tree-sitter (vendored web-tree-sitter) plus
3
+ * gzip-compressed grammar binaries shipped in vendor/grammars. No native compilation,
4
+ * no network, no per-platform binaries — the same bytes run on every OS and Node ≥ 22.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import zlib from 'node:zlib';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { Parser, Language, Query } from '../../vendor/web-tree-sitter/web-tree-sitter.js';
11
+
12
+ const VENDOR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../vendor');
13
+ const RUNTIME_WASM = path.join(VENDOR, 'web-tree-sitter', 'web-tree-sitter.wasm');
14
+ const GRAMMAR_DIR = path.join(VENDOR, 'grammars');
15
+
16
+ let initPromise = null;
17
+ const grammarCache = new Map(); // grammar name -> Promise<Language>
18
+
19
+ export function initRuntime() {
20
+ initPromise ??= Parser.init({ locateFile: () => RUNTIME_WASM });
21
+ return initPromise;
22
+ }
23
+
24
+ export function availableGrammars() {
25
+ return fs.readdirSync(GRAMMAR_DIR).filter(f => f.endsWith('.wasm.gz')).map(f => f.slice(0, -'.wasm.gz'.length));
26
+ }
27
+
28
+ export function loadGrammar(name) {
29
+ let p = grammarCache.get(name);
30
+ if (!p) {
31
+ p = (async () => {
32
+ await initRuntime();
33
+ const bytes = zlib.gunzipSync(fs.readFileSync(path.join(GRAMMAR_DIR, `${name}.wasm.gz`)));
34
+ return Language.load(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength));
35
+ })();
36
+ grammarCache.set(name, p);
37
+ }
38
+ return p;
39
+ }
40
+
41
+ /** Compile a query, reporting the offending grammar on failure (queries are per-grammar). */
42
+ export function compileQuery(language, source, label) {
43
+ try {
44
+ return new Query(language, source);
45
+ } catch (err) {
46
+ throw new Error(`query compile failed for ${label}: ${err.message}`);
47
+ }
48
+ }
49
+
50
+ export function createParser(language) {
51
+ const parser = new Parser();
52
+ parser.setLanguage(language);
53
+ return parser;
54
+ }
55
+
56
+ export { Parser, Language, Query };