@optave/codegraph 3.8.0 → 3.9.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/README.md +13 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +137 -86
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +16 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +31 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +7 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +100 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +4 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +51 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +64 -59
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +352 -107
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +49 -18
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +2 -2
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +3 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +141 -89
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +23 -8
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +65 -54
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +176 -104
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +34 -0
- package/src/db/repository/native-repository.ts +104 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +55 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +77 -81
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +409 -99
- package/src/domain/graph/builder/stages/build-edges.ts +45 -19
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +143 -89
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +26 -7
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +66 -54
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +26 -5
package/src/domain/parser.ts
CHANGED
|
@@ -102,6 +102,12 @@ let _cachedLanguages: Map<string, Language> | null = null;
|
|
|
102
102
|
// Query cache for JS/TS/TSX extractors (populated during createParsers)
|
|
103
103
|
const _queryCache: Map<string, Query> = new Map();
|
|
104
104
|
|
|
105
|
+
// Tracks whether ALL grammars have been loaded (vs. a lazy subset)
|
|
106
|
+
let _allParsersLoaded: boolean = false;
|
|
107
|
+
|
|
108
|
+
// In-flight grammar loads keyed by language id — prevents concurrent duplicate loads
|
|
109
|
+
const _loadingPromises: Map<string, Promise<void>> = new Map();
|
|
110
|
+
|
|
105
111
|
// Extensions that need typeMap backfill (type annotations only exist in TS/TSX)
|
|
106
112
|
const TS_BACKFILL_EXTS = new Set(['.ts', '.tsx']);
|
|
107
113
|
|
|
@@ -150,42 +156,87 @@ const TS_EXTRA_PATTERNS: string[] = [
|
|
|
150
156
|
'(type_alias_declaration name: (type_identifier) @type_name) @type_node',
|
|
151
157
|
];
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Load a single language grammar and cache the parser + language + query.
|
|
161
|
+
* Uses in-flight deduplication so concurrent callers awaiting the same grammar
|
|
162
|
+
* share a single load rather than producing orphaned WASM instances.
|
|
163
|
+
* Assumes Parser.init() has already been called and _cachedParsers/_cachedLanguages exist.
|
|
164
|
+
*/
|
|
165
|
+
async function loadLanguage(entry: LanguageRegistryEntry): Promise<void> {
|
|
166
|
+
if (_cachedParsers!.has(entry.id)) return;
|
|
167
|
+
const inflight = _loadingPromises.get(entry.id);
|
|
168
|
+
if (inflight) return inflight;
|
|
169
|
+
const p = doLoadLanguage(entry).finally(() => _loadingPromises.delete(entry.id));
|
|
170
|
+
_loadingPromises.set(entry.id, p);
|
|
171
|
+
return p;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
|
|
175
|
+
try {
|
|
176
|
+
const lang = await Language.load(grammarPath(entry.grammarFile));
|
|
177
|
+
const parser = new Parser();
|
|
178
|
+
parser.setLanguage(lang);
|
|
179
|
+
_cachedParsers!.set(entry.id, parser);
|
|
180
|
+
_cachedLanguages!.set(entry.id, lang);
|
|
181
|
+
if (entry.extractor === extractSymbols && !_queryCache.has(entry.id)) {
|
|
182
|
+
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
183
|
+
const patterns = isTS
|
|
184
|
+
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
185
|
+
: [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
|
|
186
|
+
_queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
187
|
+
}
|
|
188
|
+
} catch (e: unknown) {
|
|
189
|
+
if (entry.required) throw e;
|
|
190
|
+
warn(
|
|
191
|
+
`${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
|
|
192
|
+
);
|
|
193
|
+
_cachedParsers!.set(entry.id, null);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
|
|
197
|
+
async function initParserRuntime(): Promise<void> {
|
|
156
198
|
if (!_initialized) {
|
|
157
199
|
await Parser.init();
|
|
158
200
|
_initialized = true;
|
|
159
201
|
}
|
|
202
|
+
if (!_cachedParsers) _cachedParsers = new Map();
|
|
203
|
+
if (!_cachedLanguages) _cachedLanguages = new Map();
|
|
204
|
+
}
|
|
160
205
|
|
|
161
|
-
|
|
162
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Load only the WASM grammars needed for the given file paths.
|
|
208
|
+
* Grammars already in cache are reused. This avoids the ~500ms cold-start
|
|
209
|
+
* penalty of loading all 23+ grammars when only 1-2 are needed (e.g. incremental rebuilds).
|
|
210
|
+
*/
|
|
211
|
+
async function ensureParsersForFiles(filePaths: string[]): Promise<Map<string, Parser | null>> {
|
|
212
|
+
await initParserRuntime();
|
|
213
|
+
const needed = new Set<LanguageRegistryEntry>();
|
|
214
|
+
for (const fp of filePaths) {
|
|
215
|
+
const ext = path.extname(fp).toLowerCase();
|
|
216
|
+
const entry = _extToLang.get(ext);
|
|
217
|
+
if (entry && !_cachedParsers!.has(entry.id)) needed.add(entry);
|
|
218
|
+
}
|
|
219
|
+
for (const entry of needed) {
|
|
220
|
+
await loadLanguage(entry);
|
|
221
|
+
}
|
|
222
|
+
return _cachedParsers!;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load ALL WASM grammars. Used by full builds and feature modules (CFG, dataflow, complexity)
|
|
227
|
+
* that may process files of any language.
|
|
228
|
+
*/
|
|
229
|
+
export async function createParsers(): Promise<Map<string, Parser | null>> {
|
|
230
|
+
if (_cachedParsers && _allParsersLoaded) return _cachedParsers;
|
|
231
|
+
|
|
232
|
+
await initParserRuntime();
|
|
163
233
|
for (const entry of LANGUAGE_REGISTRY) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const parser = new Parser();
|
|
167
|
-
parser.setLanguage(lang);
|
|
168
|
-
parsers.set(entry.id, parser);
|
|
169
|
-
languages.set(entry.id, lang);
|
|
170
|
-
// Compile and cache tree-sitter Query for JS/TS/TSX extractors
|
|
171
|
-
if (entry.extractor === extractSymbols && !_queryCache.has(entry.id)) {
|
|
172
|
-
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
173
|
-
const patterns = isTS
|
|
174
|
-
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
175
|
-
: [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
|
|
176
|
-
_queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
177
|
-
}
|
|
178
|
-
} catch (e: unknown) {
|
|
179
|
-
if (entry.required) throw e;
|
|
180
|
-
warn(
|
|
181
|
-
`${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
|
|
182
|
-
);
|
|
183
|
-
parsers.set(entry.id, null);
|
|
234
|
+
if (!_cachedParsers!.has(entry.id)) {
|
|
235
|
+
await loadLanguage(entry);
|
|
184
236
|
}
|
|
185
237
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return parsers;
|
|
238
|
+
_allParsersLoaded = true;
|
|
239
|
+
return _cachedParsers!;
|
|
189
240
|
}
|
|
190
241
|
|
|
191
242
|
/**
|
|
@@ -193,42 +244,32 @@ export async function createParsers(): Promise<Map<string, Parser | null>> {
|
|
|
193
244
|
* Call this between repeated builds in the same process (e.g. benchmarks)
|
|
194
245
|
* to prevent memory accumulation that can cause segfaults.
|
|
195
246
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (parser && typeof parser.delete === 'function') {
|
|
200
|
-
try {
|
|
201
|
-
parser.delete();
|
|
202
|
-
} catch (e: unknown) {
|
|
203
|
-
debug(`Failed to dispose parser ${id}: ${(e as Error).message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
_cachedParsers = null;
|
|
208
|
-
}
|
|
209
|
-
for (const [id, query] of _queryCache) {
|
|
210
|
-
if (query && typeof query.delete === 'function') {
|
|
247
|
+
function disposeMapEntries(entries: Iterable<[string, any]>, label: string): void {
|
|
248
|
+
for (const [id, item] of entries) {
|
|
249
|
+
if (item && typeof item.delete === 'function') {
|
|
211
250
|
try {
|
|
212
|
-
|
|
251
|
+
item.delete();
|
|
213
252
|
} catch (e: unknown) {
|
|
214
|
-
debug(`Failed to dispose
|
|
253
|
+
debug(`Failed to dispose ${label} ${id}: ${(e as Error).message}`);
|
|
215
254
|
}
|
|
216
255
|
}
|
|
217
256
|
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function disposeParsers(): void {
|
|
260
|
+
if (_cachedParsers) {
|
|
261
|
+
disposeMapEntries(_cachedParsers, 'parser');
|
|
262
|
+
_cachedParsers = null;
|
|
263
|
+
}
|
|
264
|
+
disposeMapEntries(_queryCache, 'query');
|
|
218
265
|
_queryCache.clear();
|
|
219
266
|
if (_cachedLanguages) {
|
|
220
|
-
|
|
221
|
-
if (lang && typeof (lang as any).delete === 'function') {
|
|
222
|
-
try {
|
|
223
|
-
(lang as any).delete();
|
|
224
|
-
} catch (e: unknown) {
|
|
225
|
-
debug(`Failed to dispose language ${id}: ${(e as Error).message}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
267
|
+
disposeMapEntries(_cachedLanguages, 'language');
|
|
229
268
|
_cachedLanguages = null;
|
|
230
269
|
}
|
|
231
270
|
_initialized = false;
|
|
271
|
+
_allParsersLoaded = false;
|
|
272
|
+
_loadingPromises.clear();
|
|
232
273
|
}
|
|
233
274
|
|
|
234
275
|
export function getParser(parsers: Map<string, Parser | null>, filePath: string): Parser | null {
|
|
@@ -247,20 +288,15 @@ export async function ensureWasmTrees(
|
|
|
247
288
|
fileSymbols: Map<string, any>,
|
|
248
289
|
rootDir: string,
|
|
249
290
|
): Promise<void> {
|
|
250
|
-
//
|
|
251
|
-
|
|
291
|
+
// Single pass: collect absolute paths for files that need parsing
|
|
292
|
+
const filePaths: string[] = [];
|
|
252
293
|
for (const [relPath, symbols] of fileSymbols) {
|
|
253
|
-
if (!symbols._tree) {
|
|
254
|
-
|
|
255
|
-
if (_extToLang.has(ext)) {
|
|
256
|
-
needsParse = true;
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
294
|
+
if (!symbols._tree && _extToLang.has(path.extname(relPath).toLowerCase())) {
|
|
295
|
+
filePaths.push(path.join(rootDir, relPath));
|
|
259
296
|
}
|
|
260
297
|
}
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
const parsers = await createParsers();
|
|
298
|
+
if (filePaths.length === 0) return;
|
|
299
|
+
const parsers = await ensureParsersForFiles(filePaths);
|
|
264
300
|
|
|
265
301
|
for (const [relPath, symbols] of fileSymbols) {
|
|
266
302
|
if (symbols._tree) continue;
|
|
@@ -349,17 +385,19 @@ function patchImports(imports: any[]): void {
|
|
|
349
385
|
}
|
|
350
386
|
}
|
|
351
387
|
|
|
352
|
-
/** Normalize native typeMap array to a Map instance.
|
|
388
|
+
/** Normalize native typeMap array to a Map instance.
|
|
389
|
+
* Uses first-wins semantics at equal confidence to match the WASM/JS extractor. */
|
|
353
390
|
function patchTypeMap(r: any): void {
|
|
354
391
|
if (!r.typeMap) {
|
|
355
392
|
r.typeMap = new Map();
|
|
356
393
|
} else if (!(r.typeMap instanceof Map)) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
{ type: e.typeName, confidence: 0.9 }
|
|
361
|
-
|
|
362
|
-
|
|
394
|
+
const map = new Map<string, TypeMapEntry>();
|
|
395
|
+
for (const e of r.typeMap as Array<{ name: string; typeName: string }>) {
|
|
396
|
+
if (!map.has(e.name)) {
|
|
397
|
+
map.set(e.name, { type: e.typeName, confidence: (e as any).confidence ?? 0.9 });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
r.typeMap = map;
|
|
363
401
|
}
|
|
364
402
|
}
|
|
365
403
|
|
|
@@ -652,10 +690,17 @@ for (const entry of LANGUAGE_REGISTRY) {
|
|
|
652
690
|
export const SUPPORTED_EXTENSIONS: Set<string> = new Set(_extToLang.keys());
|
|
653
691
|
|
|
654
692
|
/**
|
|
655
|
-
* WASM-based typeMap backfill for
|
|
693
|
+
* WASM-based typeMap backfill for TS/TSX files parsed by the native engine.
|
|
694
|
+
* Serves two purposes:
|
|
695
|
+
* 1. Compatibility with older native binaries that don't emit typeMap (< 3.2.0).
|
|
696
|
+
* 2. Workaround for native parser scope-collision bugs — when the same variable
|
|
697
|
+
* name appears at multiple scopes, native type extraction can produce
|
|
698
|
+
* incorrect results. WASM's JS-based extractor handles scope traversal
|
|
699
|
+
* more accurately. TODO: Remove purpose (2) once the Rust extractor handles
|
|
700
|
+
* nested scopes correctly.
|
|
701
|
+
*
|
|
656
702
|
* Uses tree-sitter AST extraction instead of regex to avoid false positives from
|
|
657
703
|
* matches inside comments and string literals.
|
|
658
|
-
* TODO: Remove once all published native binaries include typeMap extraction (>= 3.2.0)
|
|
659
704
|
*/
|
|
660
705
|
async function backfillTypeMap(
|
|
661
706
|
filePath: string,
|
|
@@ -670,7 +715,7 @@ async function backfillTypeMap(
|
|
|
670
715
|
return { typeMap: new Map(), backfilled: false };
|
|
671
716
|
}
|
|
672
717
|
}
|
|
673
|
-
const parsers = await
|
|
718
|
+
const parsers = await ensureParsersForFiles([filePath]);
|
|
674
719
|
const extracted = wasmExtractSymbols(parsers, filePath, code);
|
|
675
720
|
try {
|
|
676
721
|
if (!extracted || extracted.symbols.typeMap.size === 0) {
|
|
@@ -732,23 +777,26 @@ export async function parseFileAuto(
|
|
|
732
777
|
const result = native.parseFile(filePath, source, !!opts.dataflow, opts.ast !== false);
|
|
733
778
|
if (!result) return null;
|
|
734
779
|
const patched = patchNativeResult(result);
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
|
|
780
|
+
// Always backfill typeMap for TS/TSX from WASM — native parser's type
|
|
781
|
+
// extraction can produce incorrect scope-collision results. Non-TS files
|
|
782
|
+
// are skipped to stay consistent with the batch path (backfillTypeMapBatch).
|
|
783
|
+
if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
738
784
|
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
|
|
739
|
-
|
|
740
|
-
|
|
785
|
+
if (backfilled) {
|
|
786
|
+
patched.typeMap = typeMap;
|
|
787
|
+
patched._typeMapBackfilled = true;
|
|
788
|
+
}
|
|
741
789
|
}
|
|
742
790
|
return patched;
|
|
743
791
|
}
|
|
744
792
|
|
|
745
793
|
// WASM path
|
|
746
|
-
const parsers = await
|
|
794
|
+
const parsers = await ensureParsersForFiles([filePath]);
|
|
747
795
|
const extracted = wasmExtractSymbols(parsers, filePath, source);
|
|
748
796
|
return extracted ? extracted.symbols : null;
|
|
749
797
|
}
|
|
750
798
|
|
|
751
|
-
/** Backfill typeMap via WASM for files
|
|
799
|
+
/** Backfill typeMap via WASM for TS/TSX files parsed by the native engine. */
|
|
752
800
|
async function backfillTypeMapBatch(
|
|
753
801
|
needsTypeMap: { filePath: string; relPath: string }[],
|
|
754
802
|
result: Map<string, ExtractorOutput>,
|
|
@@ -758,7 +806,7 @@ async function backfillTypeMapBatch(
|
|
|
758
806
|
);
|
|
759
807
|
if (tsFiles.length === 0) return;
|
|
760
808
|
|
|
761
|
-
const parsers = await
|
|
809
|
+
const parsers = await ensureParsersForFiles(tsFiles.map((f) => f.filePath));
|
|
762
810
|
for (const { filePath, relPath } of tsFiles) {
|
|
763
811
|
let extracted: WasmExtractResult | null | undefined;
|
|
764
812
|
try {
|
|
@@ -790,7 +838,7 @@ async function parseFilesWasm(
|
|
|
790
838
|
rootDir: string,
|
|
791
839
|
): Promise<Map<string, ExtractorOutput>> {
|
|
792
840
|
const result = new Map<string, ExtractorOutput>();
|
|
793
|
-
const parsers = await
|
|
841
|
+
const parsers = await ensureParsersForFiles(filePaths);
|
|
794
842
|
for (const filePath of filePaths) {
|
|
795
843
|
let code: string;
|
|
796
844
|
try {
|
|
@@ -831,7 +879,12 @@ export async function parseFilesAuto(
|
|
|
831
879
|
const patched = patchNativeResult(r);
|
|
832
880
|
const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
|
|
833
881
|
result.set(relPath, patched);
|
|
834
|
-
|
|
882
|
+
// Always backfill TS/TSX type maps from WASM — the native parser's type
|
|
883
|
+
// extraction can produce incorrect results when the same variable name
|
|
884
|
+
// appears at multiple scopes (e.g. `node: TreeSitterNode` in one function
|
|
885
|
+
// vs `node: NodeRow` in another). The WASM JS extractor handles scope
|
|
886
|
+
// traversal order more accurately.
|
|
887
|
+
if (TS_BACKFILL_EXTS.has(path.extname(r.file))) {
|
|
835
888
|
needsTypeMap.push({ filePath: r.file, relPath });
|
|
836
889
|
}
|
|
837
890
|
}
|
|
@@ -889,12 +942,13 @@ export async function parseFileIncremental(
|
|
|
889
942
|
const result = cache.parseFile(filePath, source);
|
|
890
943
|
if (!result) return null;
|
|
891
944
|
const patched = patchNativeResult(result);
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
if (patched.typeMap.size === 0 && TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
945
|
+
// Always backfill typeMap for TS/TSX from WASM (see parseFileAuto comment).
|
|
946
|
+
if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
895
947
|
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
|
|
896
|
-
|
|
897
|
-
|
|
948
|
+
if (backfilled) {
|
|
949
|
+
patched.typeMap = typeMap;
|
|
950
|
+
patched._typeMapBackfilled = true;
|
|
951
|
+
}
|
|
898
952
|
}
|
|
899
953
|
return patched;
|
|
900
954
|
}
|
|
@@ -100,7 +100,7 @@ export async function buildEmbeddings(
|
|
|
100
100
|
let overflowCount = 0;
|
|
101
101
|
|
|
102
102
|
for (const [file, fileNodes] of byFile) {
|
|
103
|
-
const fullPath = path.join(rootDir, file);
|
|
103
|
+
const fullPath = path.isAbsolute(file) ? file : path.join(rootDir, file);
|
|
104
104
|
let lines: string[];
|
|
105
105
|
try {
|
|
106
106
|
lines = fs.readFileSync(fullPath, 'utf-8').split('\n');
|
|
@@ -74,6 +74,7 @@ export const MODELS: Record<string, ModelConfig> = {
|
|
|
74
74
|
export const EMBEDDING_STRATEGIES: readonly string[] = ['structured', 'source'];
|
|
75
75
|
|
|
76
76
|
export const DEFAULT_MODEL: string = 'nomic-v1.5';
|
|
77
|
+
const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
77
78
|
const BATCH_SIZE_MAP: Record<string, number> = {
|
|
78
79
|
minilm: 32,
|
|
79
80
|
'jina-small': 16,
|
|
@@ -96,13 +97,28 @@ export function getModelConfig(modelKey?: string): ModelConfig {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
+
* Attempt to install a missing package.
|
|
101
|
+
* In TTY environments, prompts the user for confirmation first.
|
|
102
|
+
* In non-TTY environments (CI, piped stdin), installs automatically with a log message.
|
|
100
103
|
* Returns true if the package was installed, false otherwise.
|
|
101
|
-
* Skips the prompt entirely in non-TTY environments (CI, piped stdin).
|
|
102
104
|
* @internal Not part of the public barrel.
|
|
103
105
|
*/
|
|
104
106
|
export function promptInstall(packageName: string): Promise<boolean> {
|
|
105
|
-
if (!process.stdin.isTTY)
|
|
107
|
+
if (!process.stdin.isTTY) {
|
|
108
|
+
info(`Installing ${packageName} (optional dependency for semantic search)…`);
|
|
109
|
+
try {
|
|
110
|
+
execFileSync(NPM_BIN, ['install', '--no-save', packageName], {
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
timeout: 300_000,
|
|
113
|
+
});
|
|
114
|
+
return Promise.resolve(true);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
info(
|
|
117
|
+
`Auto-install of ${packageName} failed (${err instanceof Error ? err.message : String(err)}). Install it manually with:\n npm install ${packageName}`,
|
|
118
|
+
);
|
|
119
|
+
return Promise.resolve(false);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
106
122
|
|
|
107
123
|
return new Promise((resolve) => {
|
|
108
124
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -112,12 +128,15 @@ export function promptInstall(packageName: string): Promise<boolean> {
|
|
|
112
128
|
rl.close();
|
|
113
129
|
if (answer.trim().toLowerCase() !== 'y') return resolve(false);
|
|
114
130
|
try {
|
|
115
|
-
execFileSync(
|
|
131
|
+
execFileSync(NPM_BIN, ['install', packageName], {
|
|
116
132
|
stdio: 'inherit',
|
|
117
133
|
timeout: 300_000,
|
|
118
134
|
});
|
|
119
135
|
resolve(true);
|
|
120
|
-
} catch {
|
|
136
|
+
} catch (err) {
|
|
137
|
+
info(
|
|
138
|
+
`Install of ${packageName} failed (${err instanceof Error ? err.message : String(err)}). Install it manually with:\n npm install ${packageName}`,
|
|
139
|
+
);
|
|
121
140
|
resolve(false);
|
|
122
141
|
}
|
|
123
142
|
},
|
|
@@ -128,7 +147,7 @@ export function promptInstall(packageName: string): Promise<boolean> {
|
|
|
128
147
|
/**
|
|
129
148
|
* Lazy-load @huggingface/transformers.
|
|
130
149
|
* If the package is missing, prompts the user to install it interactively.
|
|
131
|
-
* In non-TTY environments,
|
|
150
|
+
* In non-TTY environments, attempts automatic installation.
|
|
132
151
|
* @internal Not part of the public barrel.
|
|
133
152
|
*/
|
|
134
153
|
export async function loadTransformers(): Promise<unknown> {
|
|
@@ -175,7 +194,7 @@ async function loadModel(modelKey?: string): Promise<{ extractor: unknown; confi
|
|
|
175
194
|
pipeline = transformers.pipeline;
|
|
176
195
|
|
|
177
196
|
info(`Loading embedding model: ${config.name} (${config.dim}d)...`);
|
|
178
|
-
const pipelineOpts = config.quantized ? {
|
|
197
|
+
const pipelineOpts = config.quantized ? { dtype: 'q8' } : {};
|
|
179
198
|
try {
|
|
180
199
|
extractor =
|
|
181
200
|
await // biome-ignore lint/complexity/noBannedTypes: dynamically loaded transformers pipeline is untyped
|
|
@@ -25,45 +25,51 @@ export interface HybridSearchResult {
|
|
|
25
25
|
results: HybridResult[];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.split(';')
|
|
43
|
-
.map((q) => q.trim())
|
|
44
|
-
.filter((q) => q.length > 0)
|
|
45
|
-
: [query];
|
|
28
|
+
interface RankedItem {
|
|
29
|
+
key: string;
|
|
30
|
+
rank: number;
|
|
31
|
+
source: 'bm25' | 'semantic';
|
|
32
|
+
name: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
file: string;
|
|
35
|
+
line: number;
|
|
36
|
+
endLine?: number | null;
|
|
37
|
+
role?: string | null;
|
|
38
|
+
fileHash?: string | null;
|
|
39
|
+
bm25Score?: number;
|
|
40
|
+
similarity?: number;
|
|
41
|
+
}
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
interface FusionEntry {
|
|
44
|
+
name: string;
|
|
45
|
+
kind: string;
|
|
46
|
+
file: string;
|
|
47
|
+
line: number;
|
|
48
|
+
endLine: number | null;
|
|
49
|
+
role: string | null;
|
|
50
|
+
fileHash: string | null;
|
|
51
|
+
rrfScore: number;
|
|
52
|
+
bm25Score: number | null;
|
|
53
|
+
bm25Rank: number | null;
|
|
54
|
+
similarity: number | null;
|
|
55
|
+
semanticRank: number | null;
|
|
56
|
+
}
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
line: number;
|
|
60
|
-
endLine?: number | null;
|
|
61
|
-
role?: string | null;
|
|
62
|
-
fileHash?: string | null;
|
|
63
|
-
bm25Score?: number;
|
|
64
|
-
similarity?: number;
|
|
65
|
-
}
|
|
58
|
+
/** Parse a semicolon-delimited query string into individual queries. */
|
|
59
|
+
function parseQueries(query: string): string[] {
|
|
60
|
+
return query
|
|
61
|
+
.split(';')
|
|
62
|
+
.map((q) => q.trim())
|
|
63
|
+
.filter((q) => q.length > 0);
|
|
64
|
+
}
|
|
66
65
|
|
|
66
|
+
/** Collect BM25 and semantic ranked lists for each query. */
|
|
67
|
+
async function collectRankedLists(
|
|
68
|
+
queries: string[],
|
|
69
|
+
customDbPath: string | undefined,
|
|
70
|
+
opts: SemanticSearchOpts,
|
|
71
|
+
topK: number,
|
|
72
|
+
): Promise<RankedItem[][]> {
|
|
67
73
|
const rankedLists: RankedItem[][] = [];
|
|
68
74
|
|
|
69
75
|
for (const q of queries) {
|
|
@@ -96,22 +102,13 @@ export async function hybridSearchData(
|
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
kind: string;
|
|
102
|
-
file: string;
|
|
103
|
-
line: number;
|
|
104
|
-
endLine: number | null;
|
|
105
|
-
role: string | null;
|
|
106
|
-
fileHash: string | null;
|
|
107
|
-
rrfScore: number;
|
|
108
|
-
bm25Score: number | null;
|
|
109
|
-
bm25Rank: number | null;
|
|
110
|
-
similarity: number | null;
|
|
111
|
-
semanticRank: number | null;
|
|
112
|
-
}
|
|
105
|
+
return rankedLists;
|
|
106
|
+
}
|
|
113
107
|
|
|
108
|
+
/** Reciprocal Rank Fusion: merge ranked lists into a single scored result set. */
|
|
109
|
+
function fuseResults(rankedLists: RankedItem[][], k: number, limit: number): HybridResult[] {
|
|
114
110
|
const fusionMap = new Map<string, FusionEntry>();
|
|
111
|
+
|
|
115
112
|
for (const list of rankedLists) {
|
|
116
113
|
for (const item of list) {
|
|
117
114
|
if (!fusionMap.has(item.key)) {
|
|
@@ -146,7 +143,7 @@ export async function hybridSearchData(
|
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
|
|
146
|
+
return [...fusionMap.values()]
|
|
150
147
|
.sort((a, b) => b.rrfScore - a.rrfScore)
|
|
151
148
|
.slice(0, limit)
|
|
152
149
|
.map((e) => ({
|
|
@@ -163,6 +160,27 @@ export async function hybridSearchData(
|
|
|
163
160
|
similarity: e.similarity,
|
|
164
161
|
semanticRank: e.semanticRank,
|
|
165
162
|
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function hybridSearchData(
|
|
166
|
+
query: string,
|
|
167
|
+
customDbPath: string | undefined,
|
|
168
|
+
opts: SemanticSearchOpts = {},
|
|
169
|
+
): Promise<HybridSearchResult | null> {
|
|
170
|
+
const config = opts.config || loadConfig();
|
|
171
|
+
const searchCfg = config.search || ({} as CodegraphConfig['search']);
|
|
172
|
+
const limit = opts.limit ?? searchCfg.topK ?? 15;
|
|
173
|
+
const k = opts.rrfK ?? searchCfg.rrfK ?? 60;
|
|
174
|
+
const topK = (opts.limit ?? searchCfg.topK ?? 15) * 5;
|
|
175
|
+
|
|
176
|
+
const checkDb = openReadonlyOrFail(customDbPath) as BetterSqlite3Database;
|
|
177
|
+
const ftsAvailable = hasFtsIndex(checkDb);
|
|
178
|
+
checkDb.close();
|
|
179
|
+
if (!ftsAvailable) return null;
|
|
180
|
+
|
|
181
|
+
const queries = parseQueries(query);
|
|
182
|
+
const rankedLists = await collectRankedLists(queries, customDbPath, opts, topK);
|
|
183
|
+
const results = fuseResults(rankedLists, k, limit);
|
|
166
184
|
|
|
167
185
|
return { results };
|
|
168
186
|
}
|