graph-indexer 2.1.1 → 3.0.1

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 (131) hide show
  1. package/CHANGELOG.md +312 -1
  2. package/README.md +236 -312
  3. package/SECURITY.md +53 -58
  4. package/bin/graph-indexer.mjs +251 -0
  5. package/integrations/claude-code/.claude-plugin/plugin.json +9 -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 +103 -0
  9. package/integrations/claude-code/hooks/hooks.json +16 -0
  10. package/integrations/opencode/graph-indexer.js +129 -0
  11. package/package.json +32 -109
  12. package/src/cli/helper.mjs +67 -0
  13. package/src/cli/hook.mjs +366 -0
  14. package/src/cli/init.mjs +272 -0
  15. package/src/cli/resident.mjs +185 -0
  16. package/src/index/discover.mjs +128 -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 +332 -0
  21. package/src/index/resolver.mjs +961 -0
  22. package/src/mcp/render.mjs +60 -0
  23. package/src/mcp/server.mjs +147 -0
  24. package/src/mcp/tools.mjs +974 -0
  25. package/src/parse/arity.mjs +153 -0
  26. package/src/parse/extract.mjs +1038 -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 +345 -0
  40. package/src/query/facts.mjs +323 -0
  41. package/src/query/intel.mjs +683 -0
  42. package/src/query/read.mjs +81 -0
  43. package/src/query/testcmd.mjs +130 -0
  44. package/src/query/textsearch.mjs +178 -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/git.mjs +30 -0
  49. package/src/util/paths.mjs +63 -0
  50. package/src/util/regex.mjs +2 -0
  51. package/vendor/grammars/bash.wasm.gz +0 -0
  52. package/vendor/grammars/c.wasm.gz +0 -0
  53. package/vendor/grammars/c_sharp.wasm.gz +0 -0
  54. package/vendor/grammars/cpp.wasm.gz +0 -0
  55. package/vendor/grammars/css.wasm.gz +0 -0
  56. package/vendor/grammars/go.wasm.gz +0 -0
  57. package/vendor/grammars/java.wasm.gz +0 -0
  58. package/vendor/grammars/javascript.wasm.gz +0 -0
  59. package/vendor/grammars/kotlin.wasm.gz +0 -0
  60. package/vendor/grammars/manifest.json +148 -0
  61. package/vendor/grammars/php.wasm.gz +0 -0
  62. package/vendor/grammars/python.wasm.gz +0 -0
  63. package/vendor/grammars/ruby.wasm.gz +0 -0
  64. package/vendor/grammars/rust.wasm.gz +0 -0
  65. package/vendor/grammars/scala.wasm.gz +0 -0
  66. package/vendor/grammars/tsx.wasm.gz +0 -0
  67. package/vendor/grammars/typescript.wasm.gz +0 -0
  68. package/vendor/web-tree-sitter/web-tree-sitter.js +3980 -0
  69. package/vendor/web-tree-sitter/web-tree-sitter.wasm +0 -0
  70. package/cli-ui.mjs +0 -78
  71. package/colbert.mjs +0 -168
  72. package/config.mjs +0 -494
  73. package/daemon-ctl.mjs +0 -175
  74. package/daemon-lock.mjs +0 -69
  75. package/embedders/python/mlx_embed_server.py +0 -61
  76. package/embedders/python/requirements-mlx.txt +0 -5
  77. package/embedders/setup-mlx.mjs +0 -110
  78. package/embeddings.mjs +0 -571
  79. package/engine/binary.mjs +0 -437
  80. package/engine/memory.mjs +0 -1003
  81. package/engine/sqlite.mjs +0 -1136
  82. package/enrichment.mjs +0 -521
  83. package/git-signals.mjs +0 -248
  84. package/indexer.mjs +0 -546
  85. package/init.mjs +0 -1809
  86. package/layout.mjs +0 -123
  87. package/mcp/centrality.mjs +0 -117
  88. package/mcp/format.mjs +0 -61
  89. package/mcp/resolver.mjs +0 -116
  90. package/mcp/symbolgraph.mjs +0 -137
  91. package/mcp/taint.mjs +0 -225
  92. package/mcp/tools.mjs +0 -1582
  93. package/mcp/topology.mjs +0 -676
  94. package/mcp-server.mjs +0 -197
  95. package/parse/extractor.mjs +0 -628
  96. package/parse/imports.mjs +0 -365
  97. package/parse/interprocedural.mjs +0 -134
  98. package/parse/languages.mjs +0 -253
  99. package/parse/metadata.mjs +0 -701
  100. package/parse/routes.mjs +0 -327
  101. package/parse/scip.mjs +0 -391
  102. package/parse/taint-patterns.mjs +0 -119
  103. package/prompts/CORE.md +0 -91
  104. package/prompts/DOMAIN_TEMPLATE.md +0 -46
  105. package/prompts/INTEGRATION.md +0 -123
  106. package/prompts/frameworks/ANDROID.md +0 -17
  107. package/prompts/frameworks/ASPNET_CORE.md +0 -19
  108. package/prompts/frameworks/FASTAPI_DJANGO.md +0 -20
  109. package/prompts/frameworks/LARAVEL_SYMFONY.md +0 -22
  110. package/prompts/frameworks/NODE_EXPRESS_NESTJS.md +0 -19
  111. package/prompts/frameworks/RAILS.md +0 -20
  112. package/prompts/frameworks/REACT.md +0 -20
  113. package/prompts/frameworks/SPRING_BOOT.md +0 -20
  114. package/prompts/languages/BASH.md +0 -22
  115. package/prompts/languages/C.md +0 -21
  116. package/prompts/languages/CSHARP.md +0 -24
  117. package/prompts/languages/CSS_SCSS.md +0 -22
  118. package/prompts/languages/GO.md +0 -24
  119. package/prompts/languages/JAVA.md +0 -23
  120. package/prompts/languages/JAVASCRIPT_TYPESCRIPT.md +0 -24
  121. package/prompts/languages/KOTLIN.md +0 -24
  122. package/prompts/languages/PHP.md +0 -23
  123. package/prompts/languages/PYTHON.md +0 -23
  124. package/prompts/languages/RUBY.md +0 -23
  125. package/prompts/languages/RUST.md +0 -21
  126. package/prompts/languages/SWIFT.md +0 -21
  127. package/seal.mjs +0 -400
  128. package/search-core.mjs +0 -777
  129. package/search-sparse.mjs +0 -183
  130. package/storage.mjs +0 -73
  131. 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 };