sweet-search 0.0.1 → 2.3.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 (161) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +23 -0
  3. package/core/cli.js +51 -0
  4. package/core/config.js +27 -0
  5. package/core/embedding/embedding-cache.js +467 -0
  6. package/core/embedding/embedding-local-model.js +845 -0
  7. package/core/embedding/embedding-remote.js +492 -0
  8. package/core/embedding/embedding-service.js +712 -0
  9. package/core/embedding/embedding-telemetry.js +219 -0
  10. package/core/embedding/index.js +40 -0
  11. package/core/graph/community-detector.js +294 -0
  12. package/core/graph/graph-expansion.js +839 -0
  13. package/core/graph/graph-extractor.js +2304 -0
  14. package/core/graph/graph-search.js +2148 -0
  15. package/core/graph/hcgs-generator.js +666 -0
  16. package/core/graph/index.js +16 -0
  17. package/core/graph/leiden-algorithm.js +547 -0
  18. package/core/graph/relationship-resolver.js +366 -0
  19. package/core/graph/repo-map.js +408 -0
  20. package/core/graph/summary-manager.js +549 -0
  21. package/core/indexing/artifact-builder.js +1054 -0
  22. package/core/indexing/ast-chunker.js +709 -0
  23. package/core/indexing/chunking/chunk-builder.js +170 -0
  24. package/core/indexing/chunking/markdown-chunker.js +503 -0
  25. package/core/indexing/chunking/plaintext-chunker.js +104 -0
  26. package/core/indexing/dedup/dedup-phase.js +159 -0
  27. package/core/indexing/dedup/exemplar-selector.js +65 -0
  28. package/core/indexing/document-chunker.js +56 -0
  29. package/core/indexing/incremental-parser.js +390 -0
  30. package/core/indexing/incremental-tracker.js +761 -0
  31. package/core/indexing/index-codebase-v21.js +472 -0
  32. package/core/indexing/index-maintainer.mjs +1674 -0
  33. package/core/indexing/index.js +90 -0
  34. package/core/indexing/indexer-ann.js +1077 -0
  35. package/core/indexing/indexer-build.js +742 -0
  36. package/core/indexing/indexer-phases.js +800 -0
  37. package/core/indexing/indexer-pool.js +764 -0
  38. package/core/indexing/indexer-sparse-gram.js +98 -0
  39. package/core/indexing/indexer-utils.js +536 -0
  40. package/core/indexing/indexer-worker.js +148 -0
  41. package/core/indexing/li-skip-policy.js +225 -0
  42. package/core/indexing/merkle-tracker.js +244 -0
  43. package/core/indexing/model-pool.js +166 -0
  44. package/core/infrastructure/code-graph-repository.js +120 -0
  45. package/core/infrastructure/codebase-repository.js +131 -0
  46. package/core/infrastructure/config/dedup.js +54 -0
  47. package/core/infrastructure/config/embedding.js +298 -0
  48. package/core/infrastructure/config/graph.js +80 -0
  49. package/core/infrastructure/config/index.js +82 -0
  50. package/core/infrastructure/config/indexing.js +8 -0
  51. package/core/infrastructure/config/platform.js +254 -0
  52. package/core/infrastructure/config/ranking.js +221 -0
  53. package/core/infrastructure/config/search.js +396 -0
  54. package/core/infrastructure/config/translation.js +89 -0
  55. package/core/infrastructure/config/vector-store.js +114 -0
  56. package/core/infrastructure/constants.js +86 -0
  57. package/core/infrastructure/coreml-cascade.js +909 -0
  58. package/core/infrastructure/coreml-cascade.json +46 -0
  59. package/core/infrastructure/coreml-provider.js +81 -0
  60. package/core/infrastructure/db-utils.js +69 -0
  61. package/core/infrastructure/dedup-hashing.js +83 -0
  62. package/core/infrastructure/hardware-capability.js +332 -0
  63. package/core/infrastructure/index.js +104 -0
  64. package/core/infrastructure/language-patterns/maps.js +121 -0
  65. package/core/infrastructure/language-patterns/registry-core.js +323 -0
  66. package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
  67. package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
  68. package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
  69. package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
  70. package/core/infrastructure/language-patterns/registry.js +19 -0
  71. package/core/infrastructure/language-patterns.js +141 -0
  72. package/core/infrastructure/llm-provider.js +733 -0
  73. package/core/infrastructure/manifest.json +46 -0
  74. package/core/infrastructure/maxsim.wasm +0 -0
  75. package/core/infrastructure/model-fetcher.js +423 -0
  76. package/core/infrastructure/model-registry.js +214 -0
  77. package/core/infrastructure/native-inference.js +587 -0
  78. package/core/infrastructure/native-resolver.js +187 -0
  79. package/core/infrastructure/native-sparse-gram.js +257 -0
  80. package/core/infrastructure/native-tokenizer.js +160 -0
  81. package/core/infrastructure/onnx-mutex.js +45 -0
  82. package/core/infrastructure/onnx-session-utils.js +261 -0
  83. package/core/infrastructure/ort-pipeline.js +111 -0
  84. package/core/infrastructure/project-detector.js +102 -0
  85. package/core/infrastructure/quantization.js +410 -0
  86. package/core/infrastructure/simd-distance.js +502 -0
  87. package/core/infrastructure/simd-distance.wasm +0 -0
  88. package/core/infrastructure/tree-sitter-provider.js +665 -0
  89. package/core/infrastructure/webgpu-maxsim.js +222 -0
  90. package/core/query/index.js +35 -0
  91. package/core/query/intent-detector.js +201 -0
  92. package/core/query/intent-router.js +156 -0
  93. package/core/query/query-router-catboost.js +222 -0
  94. package/core/query/query-router-ml.js +266 -0
  95. package/core/query/query-router.js +213 -0
  96. package/core/ranking/cascaded-scorer.js +379 -0
  97. package/core/ranking/flashrank.js +810 -0
  98. package/core/ranking/index.js +49 -0
  99. package/core/ranking/late-interaction-index.js +2383 -0
  100. package/core/ranking/late-interaction-model.js +812 -0
  101. package/core/ranking/local-reranker.js +374 -0
  102. package/core/ranking/mmr.js +379 -0
  103. package/core/ranking/quality-scorer.js +363 -0
  104. package/core/search/context-expander.js +1167 -0
  105. package/core/search/dedup/sibling-expander.js +327 -0
  106. package/core/search/index.js +16 -0
  107. package/core/search/search-boost.js +259 -0
  108. package/core/search/search-cli.js +544 -0
  109. package/core/search/search-format.js +282 -0
  110. package/core/search/search-fusion.js +327 -0
  111. package/core/search/search-hybrid.js +204 -0
  112. package/core/search/search-pattern-chunks.js +337 -0
  113. package/core/search/search-pattern-planner.js +439 -0
  114. package/core/search/search-pattern-prefilter.js +412 -0
  115. package/core/search/search-pattern-ripgrep.js +663 -0
  116. package/core/search/search-pattern.js +463 -0
  117. package/core/search/search-postprocess.js +452 -0
  118. package/core/search/search-semantic.js +706 -0
  119. package/core/search/search-server.js +554 -0
  120. package/core/search/session-daemon-prewarm.mjs +164 -0
  121. package/core/search/session-warmup.js +595 -0
  122. package/core/search/sweet-search.js +632 -0
  123. package/core/search/warmup-metrics.js +532 -0
  124. package/core/start-server.js +6 -0
  125. package/core/training/query-router/features/extractor.js +762 -0
  126. package/core/training/query-router/features/multilingual-patterns.js +431 -0
  127. package/core/training/query-router/features/text-segmenter.js +303 -0
  128. package/core/training/query-router/features/unicode-utils.js +383 -0
  129. package/core/training/query-router/output/v45_router_d4.js +11521 -0
  130. package/core/training/query-router/output/v46_router_d4.js +11498 -0
  131. package/core/vector-store/binary-heap.js +227 -0
  132. package/core/vector-store/binary-hnsw-index.js +1004 -0
  133. package/core/vector-store/float-vector-store.js +234 -0
  134. package/core/vector-store/hnsw-index.js +580 -0
  135. package/core/vector-store/index.js +39 -0
  136. package/core/vector-store/seismic-index.js +498 -0
  137. package/core/vocabulary/index.js +84 -0
  138. package/core/vocabulary/vocab-constants.js +20 -0
  139. package/core/vocabulary/vocab-miner-extractors.js +375 -0
  140. package/core/vocabulary/vocab-miner-nl.js +404 -0
  141. package/core/vocabulary/vocab-miner-utils.js +146 -0
  142. package/core/vocabulary/vocab-miner.js +574 -0
  143. package/core/vocabulary/vocab-prewarm-cli.js +110 -0
  144. package/core/vocabulary/vocab-ranker.js +492 -0
  145. package/core/vocabulary/vocab-warmer.js +523 -0
  146. package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
  147. package/core/vocabulary/vocabulary-utils.js +704 -0
  148. package/crates/wasm-router/pkg/package.json +13 -0
  149. package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
  150. package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
  151. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
  152. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
  153. package/mcp/config-gen.js +121 -0
  154. package/mcp/server.js +335 -0
  155. package/mcp/tool-handlers.js +476 -0
  156. package/package.json +131 -9
  157. package/scripts/benchmark-harness.js +794 -0
  158. package/scripts/init.js +1058 -0
  159. package/scripts/smoke-test.js +435 -0
  160. package/scripts/uninstall.js +478 -0
  161. package/scripts/verify-runtime.js +176 -0
@@ -0,0 +1,665 @@
1
+ /**
2
+ * Tree-sitter WASM Provider
3
+ *
4
+ * Provides AST-based parsing for top languages using web-tree-sitter WASM.
5
+ * Falls back gracefully when tree-sitter or grammar files are unavailable.
6
+ *
7
+ * Usage:
8
+ * import { getTreeSitterProvider } from './core/tree-sitter-provider.js';
9
+ * const provider = getTreeSitterProvider();
10
+ * const chunks = await provider.parseFileToChunks(content, 'javascript');
11
+ * if (!chunks) { // fall back to regex }
12
+ */
13
+
14
+ // Grammar mapping: language ID -> grammar WASM file stem
15
+ const GRAMMAR_MAP = {
16
+ javascript: 'tree-sitter-javascript',
17
+ typescript: 'tree-sitter-typescript',
18
+ python: 'tree-sitter-python',
19
+ go: 'tree-sitter-go',
20
+ rust: 'tree-sitter-rust',
21
+ java: 'tree-sitter-java',
22
+ c: 'tree-sitter-c',
23
+ cpp: 'tree-sitter-cpp',
24
+ ruby: 'tree-sitter-ruby',
25
+ php: 'tree-sitter-php',
26
+ kotlin: 'tree-sitter-kotlin',
27
+ swift: 'tree-sitter-swift',
28
+ };
29
+
30
+ // Identifier node types — used to detect leaf-ident captures in extractSymbols()
31
+ const IDENT_TYPES = new Set([
32
+ 'identifier', 'type_identifier', 'property_identifier', 'field_identifier',
33
+ // Needed by Ruby, PHP, Kotlin, Swift, C++
34
+ 'constant', // Ruby class/module names
35
+ 'name', // PHP all identifiers
36
+ 'simple_identifier', // Kotlin functions, Swift functions
37
+ 'namespace_identifier', // C++ namespace names
38
+ ]);
39
+
40
+ // AST node types that represent meaningful chunk boundaries
41
+ const BOUNDARY_TYPES = new Set([
42
+ // Functions
43
+ 'function_declaration', 'function_definition', 'method_definition',
44
+ 'arrow_function', 'function_expression', 'method_declaration',
45
+ 'function_item',
46
+ // Classes
47
+ 'class_declaration', 'class_definition',
48
+ // Interfaces/Types (TypeScript)
49
+ 'interface_declaration', 'type_alias_declaration', 'enum_declaration',
50
+ // Structs/Traits (Rust/Go)
51
+ 'struct_item', 'impl_item', 'trait_item', 'type_declaration',
52
+ // Modules
53
+ 'module', 'namespace_declaration',
54
+ // Python
55
+ 'decorated_definition',
56
+ // Java
57
+ 'record_declaration', 'constructor_declaration',
58
+ // Ruby
59
+ 'singleton_method',
60
+ // PHP
61
+ 'trait_declaration',
62
+ // Kotlin
63
+ 'object_declaration',
64
+ // Swift
65
+ 'protocol_declaration', 'protocol_function_declaration', 'init_declaration',
66
+ // C
67
+ 'struct_specifier', 'enum_specifier', 'type_definition',
68
+ // C++
69
+ 'class_specifier', 'namespace_definition',
70
+ ]);
71
+
72
+ // Map tree-sitter node type -> our chunk type label
73
+ const NODE_TYPE_MAP = {
74
+ 'function_declaration': 'function',
75
+ 'function_definition': 'function',
76
+ 'function_item': 'function',
77
+ 'method_definition': 'method',
78
+ 'method_declaration': 'method',
79
+ 'arrow_function': 'arrow',
80
+ 'function_expression': 'function',
81
+ 'class_declaration': 'class',
82
+ 'class_definition': 'class',
83
+ 'interface_declaration': 'interface',
84
+ 'type_alias_declaration': 'typeAlias',
85
+ 'enum_declaration': 'enum',
86
+ 'struct_item': 'struct',
87
+ 'impl_item': 'impl',
88
+ 'trait_item': 'trait',
89
+ 'type_declaration': 'struct',
90
+ 'module': 'module',
91
+ 'namespace_declaration': 'namespace',
92
+ 'decorated_definition': 'decorator',
93
+ // Java
94
+ 'record_declaration': 'record',
95
+ 'constructor_declaration': 'method',
96
+ // Ruby
97
+ 'method': 'method',
98
+ 'singleton_method': 'method',
99
+ // PHP
100
+ 'trait_declaration': 'trait',
101
+ // Kotlin
102
+ 'object_declaration': 'class',
103
+ // Swift
104
+ 'protocol_declaration': 'interface',
105
+ 'protocol_function_declaration': 'method',
106
+ 'init_declaration': 'method',
107
+ // C
108
+ 'struct_specifier': 'struct',
109
+ 'enum_specifier': 'enum',
110
+ 'type_definition': 'typeAlias',
111
+ // C++
112
+ 'class_specifier': 'class',
113
+ 'namespace_definition': 'namespace',
114
+ };
115
+
116
+ // Standard tags.scm query patterns for symbol extraction
117
+ // These are s-expression patterns matching tree-sitter node types
118
+ const TAGS_QUERIES = {
119
+ javascript: `
120
+ (function_declaration name: (identifier) @function.definition)
121
+ (generator_function_declaration name: (identifier) @function.definition)
122
+ (class_declaration name: (identifier) @class.definition)
123
+ (method_definition name: (property_identifier) @method.definition)
124
+ (variable_declarator
125
+ name: (identifier) @arrow.definition
126
+ value: (arrow_function))
127
+ (export_statement (function_declaration name: (identifier) @function.definition))
128
+ (export_statement
129
+ declaration: (class_declaration name: (identifier) @class.definition))
130
+ (pair
131
+ key: (property_identifier) @method.definition
132
+ value: (function_expression))
133
+ (pair
134
+ key: (property_identifier) @arrow.definition
135
+ value: (arrow_function))
136
+ `,
137
+ typescript: `
138
+ (function_declaration name: (identifier) @function.definition)
139
+ (generator_function_declaration name: (identifier) @function.definition)
140
+ (class_declaration name: (type_identifier) @class.definition)
141
+ (abstract_class_declaration name: (type_identifier) @class.definition)
142
+ (method_definition name: (property_identifier) @method.definition)
143
+ (interface_declaration name: (type_identifier) @interface.definition)
144
+ (type_alias_declaration name: (type_identifier) @type.definition)
145
+ (enum_declaration name: (identifier) @enum.definition)
146
+ (variable_declarator
147
+ name: (identifier) @arrow.definition
148
+ value: (arrow_function))
149
+ (export_statement
150
+ declaration: (class_declaration name: (type_identifier) @class.definition))
151
+ (export_statement
152
+ declaration: (abstract_class_declaration name: (type_identifier) @class.definition))
153
+ (pair
154
+ key: (property_identifier) @method.definition
155
+ value: (function_expression))
156
+ (pair
157
+ key: (property_identifier) @arrow.definition
158
+ value: (arrow_function))
159
+ (module name: (identifier) @namespace.definition)
160
+ (internal_module name: (identifier) @namespace.definition)
161
+ `,
162
+ python: `
163
+ (function_definition name: (identifier) @function.definition)
164
+ (class_definition name: (identifier) @class.definition)
165
+ (decorated_definition) @decorator.definition
166
+ `,
167
+ go: `
168
+ (function_declaration name: (identifier) @function.definition)
169
+ (method_declaration name: (field_identifier) @method.definition)
170
+ (type_declaration (type_spec name: (type_identifier) @type.definition))
171
+ `,
172
+ rust: `
173
+ (function_item name: (identifier) @function.definition)
174
+ (struct_item name: (type_identifier) @struct.definition)
175
+ (impl_item type: (type_identifier) @impl.definition)
176
+ (trait_item name: (type_identifier) @trait.definition)
177
+ (enum_item name: (type_identifier) @enum.definition)
178
+ `,
179
+ java: `
180
+ (class_declaration name: (identifier) @class.definition)
181
+ (interface_declaration name: (identifier) @interface.definition)
182
+ (enum_declaration name: (identifier) @enum.definition)
183
+ (record_declaration name: (identifier) @record.definition)
184
+ (method_declaration name: (identifier) @method.definition)
185
+ (constructor_declaration name: (identifier) @method.definition)
186
+ `,
187
+ ruby: `
188
+ (class name: (constant) @class.definition)
189
+ (module name: (constant) @module.definition)
190
+ (method name: (identifier) @method.definition)
191
+ (singleton_method name: (identifier) @method.definition)
192
+ `,
193
+ php: `
194
+ (class_declaration name: (name) @class.definition)
195
+ (interface_declaration name: (name) @interface.definition)
196
+ (enum_declaration name: (name) @enum.definition)
197
+ (trait_declaration name: (name) @trait.definition)
198
+ (function_definition name: (name) @function.definition)
199
+ (method_declaration name: (name) @method.definition)
200
+ `,
201
+ // Kotlin: positional children — no `name:` field on declarations
202
+ kotlin: `
203
+ (class_declaration (type_identifier) @class.definition)
204
+ (object_declaration (type_identifier) @object.definition)
205
+ (function_declaration (simple_identifier) @function.definition)
206
+ `,
207
+ // Swift: init_declaration has no name child — captured at node level
208
+ swift: `
209
+ (class_declaration name: (type_identifier) @class.definition)
210
+ (protocol_declaration name: (type_identifier) @interface.definition)
211
+ (function_declaration name: (simple_identifier) @function.definition)
212
+ (protocol_function_declaration name: (simple_identifier) @method.definition)
213
+ (init_declaration) @method.definition
214
+ `,
215
+ // C/C++: function name nested inside declarator chain
216
+ c: `
217
+ (function_definition
218
+ declarator: (function_declarator
219
+ declarator: (identifier) @function.definition))
220
+ (struct_specifier name: (type_identifier) @struct.definition)
221
+ (enum_specifier name: (type_identifier) @enum.definition)
222
+ (type_definition declarator: (type_identifier) @type.definition)
223
+ `,
224
+ cpp: `
225
+ (function_definition
226
+ declarator: (function_declarator
227
+ declarator: (identifier) @function.definition))
228
+ (class_specifier name: (type_identifier) @class.definition)
229
+ (struct_specifier name: (type_identifier) @struct.definition)
230
+ (enum_specifier name: (type_identifier) @enum.definition)
231
+ (namespace_definition name: (namespace_identifier) @namespace.definition)
232
+ `,
233
+ };
234
+
235
+ // Map capture names from tags.scm queries to entity types
236
+ const CAPTURE_TO_ENTITY_TYPE = {
237
+ 'function.definition': 'function',
238
+ 'class.definition': 'class',
239
+ 'method.definition': 'method',
240
+ 'interface.definition': 'interface',
241
+ 'type.definition': 'typeAlias',
242
+ 'enum.definition': 'enum',
243
+ 'struct.definition': 'struct',
244
+ 'impl.definition': 'impl',
245
+ 'trait.definition': 'trait',
246
+ 'arrow.definition': 'arrowFunction',
247
+ 'decorator.definition': 'decorator',
248
+ 'namespace.definition': 'namespace',
249
+ // New: Java, Ruby, PHP, Kotlin
250
+ 'record.definition': 'record',
251
+ 'module.definition': 'module',
252
+ 'object.definition': 'class',
253
+ };
254
+
255
+ export class TreeSitterProvider {
256
+ constructor(options = {}) {
257
+ this.grammarsDir = options.grammarsDir || null;
258
+ this._parser = null;
259
+ this._languages = new Map();
260
+ this._initPromise = null;
261
+ this._available = null; // null = unknown, true/false after first check
262
+ this._chunkCounter = 0; // per-parse chunk ID counter
263
+ }
264
+
265
+ /** Check if web-tree-sitter is importable */
266
+ async isAvailable() {
267
+ if (this._available !== null) return this._available;
268
+ try {
269
+ await import('web-tree-sitter');
270
+ this._available = true;
271
+ } catch {
272
+ this._available = false;
273
+ }
274
+ return this._available;
275
+ }
276
+
277
+ /** Lazily initialize the tree-sitter parser (once) */
278
+ async init() {
279
+ if (this._parser) return this._parser;
280
+ if (this._initPromise) return this._initPromise;
281
+
282
+ this._initPromise = (async () => {
283
+ try {
284
+ const { Parser } = await import('web-tree-sitter');
285
+ await Parser.init();
286
+ this._parser = new Parser();
287
+ return this._parser;
288
+ } catch (err) {
289
+ this._available = false;
290
+ this._initPromise = null;
291
+ return null;
292
+ }
293
+ })();
294
+
295
+ return this._initPromise;
296
+ }
297
+
298
+ /** Load a language grammar (lazy, cached) */
299
+ async loadLanguage(languageId) {
300
+ if (this._languages.has(languageId)) return this._languages.get(languageId);
301
+
302
+ const grammarName = GRAMMAR_MAP[languageId];
303
+ if (!grammarName) return null;
304
+
305
+ try {
306
+ const parser = await this.init();
307
+ if (!parser) return null;
308
+
309
+ const wasmPath = await this._findGrammarWasm(languageId, grammarName);
310
+ if (!wasmPath) return null;
311
+
312
+ const { Language } = await import('web-tree-sitter');
313
+ const language = await Language.load(wasmPath);
314
+ this._languages.set(languageId, language);
315
+ return language;
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+
321
+ /** Parse content with tree-sitter, returns tree or null */
322
+ async parse(content, languageId) {
323
+ const language = await this.loadLanguage(languageId);
324
+ if (!language) return null;
325
+
326
+ this._parser.setLanguage(language);
327
+ return this._parser.parse(content);
328
+ }
329
+
330
+ /**
331
+ * Extract symbols from content using tree-sitter tags.scm query patterns.
332
+ * Returns an array of symbol objects, or null if tree-sitter is unavailable
333
+ * or the language is unsupported.
334
+ *
335
+ * @param {string} content - Source code content
336
+ * @param {string} languageId - Language identifier (e.g. 'javascript')
337
+ * @returns {Promise<Array<{name: string, type: string, startLine: number, endLine: number, signature: string}>|null>}
338
+ */
339
+ async extractSymbols(content, languageId) {
340
+ if (!(await this.isAvailable())) return null;
341
+
342
+ const queryString = TAGS_QUERIES[languageId];
343
+ if (!queryString) return null;
344
+
345
+ const language = await this.loadLanguage(languageId);
346
+ if (!language) return null;
347
+
348
+ let tree;
349
+ let query;
350
+ try {
351
+ this._parser.setLanguage(language);
352
+ tree = this._parser.parse(content);
353
+ if (!tree) return null;
354
+
355
+ query = await this._createQuery(language, queryString);
356
+ const captures = query.captures(tree.rootNode);
357
+
358
+ const symbols = [];
359
+ const seen = new Set(); // deduplicate by startIndex
360
+ for (const capture of captures) {
361
+ const { name: captureName, node } = capture;
362
+ const entityType = CAPTURE_TO_ENTITY_TYPE[captureName];
363
+ if (!entityType) continue;
364
+
365
+ // When queries capture an identifier (e.g. `name: (identifier) @x`),
366
+ // the node is the identifier leaf — use node.text for the name and
367
+ // node.parent for the extent (start/end lines, signature).
368
+ const isLeafIdent = IDENT_TYPES.has(node.type);
369
+ const extentNode = isLeafIdent && node.parent ? node.parent : node;
370
+
371
+ // Deduplicate: multiple captures can match the same declaration
372
+ const key = `${extentNode.startIndex}:${entityType}`;
373
+ if (seen.has(key)) continue;
374
+ seen.add(key);
375
+
376
+ const startLine = extentNode.startPosition.row;
377
+ const endLine = extentNode.endPosition.row;
378
+
379
+ // Build signature from the extent node's first line
380
+ const nodeText = content.substring(extentNode.startIndex, extentNode.endIndex);
381
+ const firstLine = nodeText.split('\n')[0].trim();
382
+ const signature = firstLine.length > 120
383
+ ? firstLine.substring(0, 117) + '...'
384
+ : firstLine;
385
+
386
+ const symbolName = isLeafIdent
387
+ ? node.text
388
+ : (node.childForFieldName?.('name')?.text
389
+ || this._extractNodeName(node)
390
+ || `<anonymous:${entityType}>`);
391
+
392
+ symbols.push({
393
+ name: symbolName,
394
+ type: entityType,
395
+ startLine,
396
+ endLine,
397
+ signature,
398
+ });
399
+ }
400
+
401
+ return symbols;
402
+ } catch {
403
+ return null;
404
+ } finally {
405
+ if (query) query.delete();
406
+ if (tree) tree.delete();
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Parse file content into semantic chunks using the cAST recursive algorithm.
412
+ * Returns array of chunk objects or null if tree-sitter can't handle it.
413
+ */
414
+ async parseFileToChunks(content, languageId, options = {}) {
415
+ const tree = await this.parse(content, languageId);
416
+ if (!tree) return null;
417
+
418
+ const maxChunkSize = options.maxChunkSize || 2000;
419
+ this._chunkCounter = 0;
420
+
421
+ const children = this._getChildren(tree.rootNode);
422
+ const chunks = this.recursiveChunk(children, content, maxChunkSize, null);
423
+
424
+ tree.delete(); // free WASM memory
425
+ return chunks.length > 0 ? chunks : null;
426
+ }
427
+
428
+ /** Generate a unique chunk ID for this parse session */
429
+ _nextChunkId() {
430
+ return `c${++this._chunkCounter}`;
431
+ }
432
+
433
+ /** Collect children of a node into an array */
434
+ _getChildren(node) {
435
+ const children = [];
436
+ for (let i = 0; i < node.childCount; i++) {
437
+ children.push(node.child(i));
438
+ }
439
+ return children;
440
+ }
441
+
442
+ /**
443
+ * cAST recursive split-merge algorithm.
444
+ *
445
+ * Greedily merges adjacent sibling AST nodes into chunks up to maxSize.
446
+ * When a single node exceeds maxSize, recurses into its children.
447
+ * Never splits mid-expression or mid-statement (leaf nodes emit as-is).
448
+ *
449
+ * @param {Array} nodes - Sibling AST nodes to chunk
450
+ * @param {string} content - Full file content
451
+ * @param {number} maxSize - Maximum chunk size in characters
452
+ * @param {object|null} parentInfo - Parent chunk info for hierarchical linking
453
+ * @returns {Array} List of chunk objects
454
+ */
455
+ recursiveChunk(nodes, content, maxSize, parentInfo) {
456
+ const chunks = [];
457
+ let buffer = [];
458
+ let bufferSize = 0;
459
+
460
+ const flushBuffer = () => {
461
+ if (buffer.length === 0) return;
462
+ const text = buffer
463
+ .map(n => content.substring(n.startIndex, n.endIndex))
464
+ .join('\n');
465
+
466
+ if (text.trim().length > 30) {
467
+ const firstBoundary = buffer.find(n => BOUNDARY_TYPES.has(n.type));
468
+ const name = firstBoundary ? this._extractNodeName(firstBoundary) : null;
469
+ const type = firstBoundary ? (NODE_TYPE_MAP[firstBoundary.type] || 'code') : 'code';
470
+
471
+ chunks.push({
472
+ chunkId: this._nextChunkId(),
473
+ parentChunkId: parentInfo?.chunkId || null,
474
+ parentSymbol: parentInfo?.name || null,
475
+ parentType: parentInfo?.type || null,
476
+ text: text.trim(),
477
+ startLine: buffer[0].startPosition.row,
478
+ endLine: buffer[buffer.length - 1].endPosition.row,
479
+ type,
480
+ name: name || (buffer.length === 1 ? null : null),
481
+ });
482
+ }
483
+ buffer = [];
484
+ bufferSize = 0;
485
+ };
486
+
487
+ for (const node of nodes) {
488
+ const nodeSize = node.endIndex - node.startIndex;
489
+
490
+ if (bufferSize + nodeSize <= maxSize) {
491
+ // Fits in current buffer — accumulate
492
+ buffer.push(node);
493
+ bufferSize += nodeSize;
494
+ } else {
495
+ // Doesn't fit — flush buffer first
496
+ flushBuffer();
497
+
498
+ if (nodeSize <= maxSize) {
499
+ // Node fits alone — start new buffer
500
+ buffer = [node];
501
+ bufferSize = nodeSize;
502
+ } else {
503
+ // Node is oversized even alone — recurse into children
504
+ if (node.childCount > 0) {
505
+ const name = this._extractNodeName(node);
506
+ const type = NODE_TYPE_MAP[node.type] || 'code';
507
+
508
+ // Transparent nodes (e.g., statement_block, block) that have no
509
+ // name and aren't boundary types should pass through the caller's
510
+ // parent context instead of creating an anonymous level.
511
+ let subParent;
512
+ if (!name && !BOUNDARY_TYPES.has(node.type) && parentInfo) {
513
+ subParent = parentInfo;
514
+ } else {
515
+ const parentId = this._nextChunkId();
516
+ subParent = { chunkId: parentId, name: name || 'unknown', type };
517
+ }
518
+
519
+ const subChunks = this.recursiveChunk(
520
+ this._getChildren(node),
521
+ content,
522
+ maxSize,
523
+ subParent
524
+ );
525
+ chunks.push(...subChunks);
526
+ } else {
527
+ // Leaf node too big — emit as-is (never split mid-expression)
528
+ const nodeText = content.substring(node.startIndex, node.endIndex);
529
+ chunks.push({
530
+ chunkId: this._nextChunkId(),
531
+ parentChunkId: parentInfo?.chunkId || null,
532
+ parentSymbol: parentInfo?.name || null,
533
+ parentType: parentInfo?.type || null,
534
+ text: nodeText.trim(),
535
+ startLine: node.startPosition.row,
536
+ endLine: node.endPosition.row,
537
+ type: NODE_TYPE_MAP[node.type] || 'code',
538
+ name: this._extractNodeName(node),
539
+ });
540
+ }
541
+ }
542
+ }
543
+ }
544
+
545
+ flushBuffer();
546
+ return chunks;
547
+ }
548
+
549
+ /** Extract symbol name from an AST node */
550
+ _extractNodeName(node) {
551
+ // Try field name first (most reliable)
552
+ const nameNode = node.childForFieldName('name');
553
+ if (nameNode) return nameNode.text;
554
+
555
+ // Fallback: look for identifier-type children (uses IDENT_TYPES set)
556
+ for (let i = 0; i < node.childCount; i++) {
557
+ const child = node.child(i);
558
+ if (IDENT_TYPES.has(child.type)) {
559
+ return child.text;
560
+ }
561
+ }
562
+
563
+ return null;
564
+ }
565
+
566
+ /** Create a tree-sitter query (mockable seam for tests) */
567
+ async _createQuery(language, queryString) {
568
+ const { Query } = await import('web-tree-sitter');
569
+ return new Query(language, queryString);
570
+ }
571
+
572
+ /** Find grammar WASM file on disk */
573
+ async _findGrammarWasm(languageId, grammarName) {
574
+ const fs = await import('fs');
575
+ const pathMod = await import('path');
576
+
577
+ // Strategy 1: explicit grammars directory
578
+ if (this.grammarsDir) {
579
+ const localPath = pathMod.join(this.grammarsDir, `${grammarName}.wasm`);
580
+ if (fs.existsSync(localPath)) return localPath;
581
+ }
582
+
583
+ // Strategy 2: .sweet-search/grammars/
584
+ const dataDir = process.env.SWEET_SEARCH_DATA_DIR || '.sweet-search';
585
+ const dataPath = pathMod.join(process.cwd(), dataDir, 'grammars', `${grammarName}.wasm`);
586
+ if (fs.existsSync(dataPath)) return dataPath;
587
+
588
+ // Strategy 3: tree-sitter-wasms bundle (all grammars in one package)
589
+ try {
590
+ const bundlePkg = await import.meta.resolve?.('tree-sitter-wasms/package.json');
591
+ if (bundlePkg) {
592
+ const bundleDir = pathMod.dirname(new URL(bundlePkg).pathname);
593
+ const bundlePath = pathMod.join(bundleDir, 'out', `${grammarName}.wasm`);
594
+ if (fs.existsSync(bundlePath)) return bundlePath;
595
+ }
596
+ } catch {
597
+ // tree-sitter-wasms not installed
598
+ }
599
+
600
+ // Strategy 4: individual grammar packages in node_modules
601
+ try {
602
+ const pkgPath = await import.meta.resolve?.(`${grammarName}/package.json`);
603
+ if (pkgPath) {
604
+ const pkgDir = pathMod.dirname(new URL(pkgPath).pathname);
605
+ const candidates = [
606
+ pathMod.join(pkgDir, `${grammarName}.wasm`),
607
+ pathMod.join(pkgDir, `${languageId}.wasm`),
608
+ pathMod.join(pkgDir, 'tree-sitter.wasm'),
609
+ ];
610
+ for (const candidate of candidates) {
611
+ if (fs.existsSync(candidate)) return candidate;
612
+ }
613
+ }
614
+ } catch {
615
+ // Package not installed
616
+ }
617
+
618
+ return null;
619
+ }
620
+
621
+ /** List all languages with tree-sitter grammar support */
622
+ getSupportedLanguages() {
623
+ return Object.keys(GRAMMAR_MAP);
624
+ }
625
+
626
+ /** Check if a language ID has tree-sitter grammar mapping */
627
+ hasLanguage(languageId) {
628
+ return languageId in GRAMMAR_MAP;
629
+ }
630
+
631
+ /** Reset internal state (useful for testing) */
632
+ reset() {
633
+ if (this._parser) {
634
+ try { this._parser.delete(); } catch { /* ignore */ }
635
+ }
636
+ this._parser = null;
637
+ this._languages.clear();
638
+ this._initPromise = null;
639
+ this._available = null;
640
+ }
641
+ }
642
+
643
+ // Singleton instance
644
+ let _instance = null;
645
+
646
+ export function getTreeSitterProvider(options) {
647
+ if (!_instance) {
648
+ _instance = new TreeSitterProvider(options);
649
+ } else if (options?.grammarsDir && options.grammarsDir !== _instance.grammarsDir) {
650
+ _instance.reset();
651
+ _instance = new TreeSitterProvider(options);
652
+ }
653
+ return _instance;
654
+ }
655
+
656
+ /** Reset the singleton (for testing) */
657
+ export function resetTreeSitterProvider() {
658
+ if (_instance) {
659
+ _instance.reset();
660
+ _instance = null;
661
+ }
662
+ }
663
+
664
+ // Re-export constants for testing
665
+ export { GRAMMAR_MAP, IDENT_TYPES, BOUNDARY_TYPES, NODE_TYPE_MAP, TAGS_QUERIES, CAPTURE_TO_ENTITY_TYPE };