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.
- package/LICENSE +190 -0
- package/NOTICE +23 -0
- package/core/cli.js +51 -0
- package/core/config.js +27 -0
- package/core/embedding/embedding-cache.js +467 -0
- package/core/embedding/embedding-local-model.js +845 -0
- package/core/embedding/embedding-remote.js +492 -0
- package/core/embedding/embedding-service.js +712 -0
- package/core/embedding/embedding-telemetry.js +219 -0
- package/core/embedding/index.js +40 -0
- package/core/graph/community-detector.js +294 -0
- package/core/graph/graph-expansion.js +839 -0
- package/core/graph/graph-extractor.js +2304 -0
- package/core/graph/graph-search.js +2148 -0
- package/core/graph/hcgs-generator.js +666 -0
- package/core/graph/index.js +16 -0
- package/core/graph/leiden-algorithm.js +547 -0
- package/core/graph/relationship-resolver.js +366 -0
- package/core/graph/repo-map.js +408 -0
- package/core/graph/summary-manager.js +549 -0
- package/core/indexing/artifact-builder.js +1054 -0
- package/core/indexing/ast-chunker.js +709 -0
- package/core/indexing/chunking/chunk-builder.js +170 -0
- package/core/indexing/chunking/markdown-chunker.js +503 -0
- package/core/indexing/chunking/plaintext-chunker.js +104 -0
- package/core/indexing/dedup/dedup-phase.js +159 -0
- package/core/indexing/dedup/exemplar-selector.js +65 -0
- package/core/indexing/document-chunker.js +56 -0
- package/core/indexing/incremental-parser.js +390 -0
- package/core/indexing/incremental-tracker.js +761 -0
- package/core/indexing/index-codebase-v21.js +472 -0
- package/core/indexing/index-maintainer.mjs +1674 -0
- package/core/indexing/index.js +90 -0
- package/core/indexing/indexer-ann.js +1077 -0
- package/core/indexing/indexer-build.js +742 -0
- package/core/indexing/indexer-phases.js +800 -0
- package/core/indexing/indexer-pool.js +764 -0
- package/core/indexing/indexer-sparse-gram.js +98 -0
- package/core/indexing/indexer-utils.js +536 -0
- package/core/indexing/indexer-worker.js +148 -0
- package/core/indexing/li-skip-policy.js +225 -0
- package/core/indexing/merkle-tracker.js +244 -0
- package/core/indexing/model-pool.js +166 -0
- package/core/infrastructure/code-graph-repository.js +120 -0
- package/core/infrastructure/codebase-repository.js +131 -0
- package/core/infrastructure/config/dedup.js +54 -0
- package/core/infrastructure/config/embedding.js +298 -0
- package/core/infrastructure/config/graph.js +80 -0
- package/core/infrastructure/config/index.js +82 -0
- package/core/infrastructure/config/indexing.js +8 -0
- package/core/infrastructure/config/platform.js +254 -0
- package/core/infrastructure/config/ranking.js +221 -0
- package/core/infrastructure/config/search.js +396 -0
- package/core/infrastructure/config/translation.js +89 -0
- package/core/infrastructure/config/vector-store.js +114 -0
- package/core/infrastructure/constants.js +86 -0
- package/core/infrastructure/coreml-cascade.js +909 -0
- package/core/infrastructure/coreml-cascade.json +46 -0
- package/core/infrastructure/coreml-provider.js +81 -0
- package/core/infrastructure/db-utils.js +69 -0
- package/core/infrastructure/dedup-hashing.js +83 -0
- package/core/infrastructure/hardware-capability.js +332 -0
- package/core/infrastructure/index.js +104 -0
- package/core/infrastructure/language-patterns/maps.js +121 -0
- package/core/infrastructure/language-patterns/registry-core.js +323 -0
- package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
- package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
- package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
- package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
- package/core/infrastructure/language-patterns/registry.js +19 -0
- package/core/infrastructure/language-patterns.js +141 -0
- package/core/infrastructure/llm-provider.js +733 -0
- package/core/infrastructure/manifest.json +46 -0
- package/core/infrastructure/maxsim.wasm +0 -0
- package/core/infrastructure/model-fetcher.js +423 -0
- package/core/infrastructure/model-registry.js +214 -0
- package/core/infrastructure/native-inference.js +587 -0
- package/core/infrastructure/native-resolver.js +187 -0
- package/core/infrastructure/native-sparse-gram.js +257 -0
- package/core/infrastructure/native-tokenizer.js +160 -0
- package/core/infrastructure/onnx-mutex.js +45 -0
- package/core/infrastructure/onnx-session-utils.js +261 -0
- package/core/infrastructure/ort-pipeline.js +111 -0
- package/core/infrastructure/project-detector.js +102 -0
- package/core/infrastructure/quantization.js +410 -0
- package/core/infrastructure/simd-distance.js +502 -0
- package/core/infrastructure/simd-distance.wasm +0 -0
- package/core/infrastructure/tree-sitter-provider.js +665 -0
- package/core/infrastructure/webgpu-maxsim.js +222 -0
- package/core/query/index.js +35 -0
- package/core/query/intent-detector.js +201 -0
- package/core/query/intent-router.js +156 -0
- package/core/query/query-router-catboost.js +222 -0
- package/core/query/query-router-ml.js +266 -0
- package/core/query/query-router.js +213 -0
- package/core/ranking/cascaded-scorer.js +379 -0
- package/core/ranking/flashrank.js +810 -0
- package/core/ranking/index.js +49 -0
- package/core/ranking/late-interaction-index.js +2383 -0
- package/core/ranking/late-interaction-model.js +812 -0
- package/core/ranking/local-reranker.js +374 -0
- package/core/ranking/mmr.js +379 -0
- package/core/ranking/quality-scorer.js +363 -0
- package/core/search/context-expander.js +1167 -0
- package/core/search/dedup/sibling-expander.js +327 -0
- package/core/search/index.js +16 -0
- package/core/search/search-boost.js +259 -0
- package/core/search/search-cli.js +544 -0
- package/core/search/search-format.js +282 -0
- package/core/search/search-fusion.js +327 -0
- package/core/search/search-hybrid.js +204 -0
- package/core/search/search-pattern-chunks.js +337 -0
- package/core/search/search-pattern-planner.js +439 -0
- package/core/search/search-pattern-prefilter.js +412 -0
- package/core/search/search-pattern-ripgrep.js +663 -0
- package/core/search/search-pattern.js +463 -0
- package/core/search/search-postprocess.js +452 -0
- package/core/search/search-semantic.js +706 -0
- package/core/search/search-server.js +554 -0
- package/core/search/session-daemon-prewarm.mjs +164 -0
- package/core/search/session-warmup.js +595 -0
- package/core/search/sweet-search.js +632 -0
- package/core/search/warmup-metrics.js +532 -0
- package/core/start-server.js +6 -0
- package/core/training/query-router/features/extractor.js +762 -0
- package/core/training/query-router/features/multilingual-patterns.js +431 -0
- package/core/training/query-router/features/text-segmenter.js +303 -0
- package/core/training/query-router/features/unicode-utils.js +383 -0
- package/core/training/query-router/output/v45_router_d4.js +11521 -0
- package/core/training/query-router/output/v46_router_d4.js +11498 -0
- package/core/vector-store/binary-heap.js +227 -0
- package/core/vector-store/binary-hnsw-index.js +1004 -0
- package/core/vector-store/float-vector-store.js +234 -0
- package/core/vector-store/hnsw-index.js +580 -0
- package/core/vector-store/index.js +39 -0
- package/core/vector-store/seismic-index.js +498 -0
- package/core/vocabulary/index.js +84 -0
- package/core/vocabulary/vocab-constants.js +20 -0
- package/core/vocabulary/vocab-miner-extractors.js +375 -0
- package/core/vocabulary/vocab-miner-nl.js +404 -0
- package/core/vocabulary/vocab-miner-utils.js +146 -0
- package/core/vocabulary/vocab-miner.js +574 -0
- package/core/vocabulary/vocab-prewarm-cli.js +110 -0
- package/core/vocabulary/vocab-ranker.js +492 -0
- package/core/vocabulary/vocab-warmer.js +523 -0
- package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
- package/core/vocabulary/vocabulary-utils.js +704 -0
- package/crates/wasm-router/pkg/package.json +13 -0
- package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
- package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
- package/mcp/config-gen.js +121 -0
- package/mcp/server.js +335 -0
- package/mcp/tool-handlers.js +476 -0
- package/package.json +131 -9
- package/scripts/benchmark-harness.js +794 -0
- package/scripts/init.js +1058 -0
- package/scripts/smoke-test.js +435 -0
- package/scripts/uninstall.js +478 -0
- 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 };
|