opencode-codebase-index 0.5.1 → 0.5.2
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 +38 -7
- package/commands/call-graph.md +24 -0
- package/dist/cli.cjs +1232 -40
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1232 -40
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1233 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1233 -41
- package/dist/index.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +2 -3
- package/skill/SKILL.md +13 -1
package/dist/cli.js
CHANGED
|
@@ -782,9 +782,15 @@ function getDefaultSearchConfig() {
|
|
|
782
782
|
minScore: 0.1,
|
|
783
783
|
includeContext: true,
|
|
784
784
|
hybridWeight: 0.5,
|
|
785
|
+
fusionStrategy: "rrf",
|
|
786
|
+
rrfK: 60,
|
|
787
|
+
rerankTopN: 20,
|
|
785
788
|
contextLines: 0
|
|
786
789
|
};
|
|
787
790
|
}
|
|
791
|
+
function isValidFusionStrategy(value) {
|
|
792
|
+
return value === "weighted" || value === "rrf";
|
|
793
|
+
}
|
|
788
794
|
function getDefaultDebugConfig() {
|
|
789
795
|
return {
|
|
790
796
|
enabled: false,
|
|
@@ -839,6 +845,9 @@ function parseConfig(raw) {
|
|
|
839
845
|
minScore: typeof rawSearch.minScore === "number" ? rawSearch.minScore : defaultSearch.minScore,
|
|
840
846
|
includeContext: typeof rawSearch.includeContext === "boolean" ? rawSearch.includeContext : defaultSearch.includeContext,
|
|
841
847
|
hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
|
|
848
|
+
fusionStrategy: isValidFusionStrategy(rawSearch.fusionStrategy) ? rawSearch.fusionStrategy : defaultSearch.fusionStrategy,
|
|
849
|
+
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
850
|
+
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
842
851
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
|
|
843
852
|
};
|
|
844
853
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -2936,6 +2945,9 @@ function hashContent(content) {
|
|
|
2936
2945
|
function hashFile(filePath) {
|
|
2937
2946
|
return native.hashFile(filePath);
|
|
2938
2947
|
}
|
|
2948
|
+
function extractCalls(content, language) {
|
|
2949
|
+
return native.extractCalls(content, language);
|
|
2950
|
+
}
|
|
2939
2951
|
var VectorStore = class {
|
|
2940
2952
|
inner;
|
|
2941
2953
|
dimensions;
|
|
@@ -3267,6 +3279,12 @@ var Database = class {
|
|
|
3267
3279
|
getChunksByFile(filePath) {
|
|
3268
3280
|
return this.inner.getChunksByFile(filePath);
|
|
3269
3281
|
}
|
|
3282
|
+
getChunksByName(name) {
|
|
3283
|
+
return this.inner.getChunksByName(name);
|
|
3284
|
+
}
|
|
3285
|
+
getChunksByNameCi(name) {
|
|
3286
|
+
return this.inner.getChunksByNameCi(name);
|
|
3287
|
+
}
|
|
3270
3288
|
deleteChunksByFile(filePath) {
|
|
3271
3289
|
return this.inner.deleteChunksByFile(filePath);
|
|
3272
3290
|
}
|
|
@@ -3310,6 +3328,73 @@ var Database = class {
|
|
|
3310
3328
|
getStats() {
|
|
3311
3329
|
return this.inner.getStats();
|
|
3312
3330
|
}
|
|
3331
|
+
// ── Symbol methods ──────────────────────────────────────────────
|
|
3332
|
+
upsertSymbol(symbol) {
|
|
3333
|
+
this.inner.upsertSymbol(symbol);
|
|
3334
|
+
}
|
|
3335
|
+
upsertSymbolsBatch(symbols) {
|
|
3336
|
+
if (symbols.length === 0) return;
|
|
3337
|
+
this.inner.upsertSymbolsBatch(symbols);
|
|
3338
|
+
}
|
|
3339
|
+
getSymbolsByFile(filePath) {
|
|
3340
|
+
return this.inner.getSymbolsByFile(filePath);
|
|
3341
|
+
}
|
|
3342
|
+
getSymbolByName(name, filePath) {
|
|
3343
|
+
return this.inner.getSymbolByName(name, filePath) ?? null;
|
|
3344
|
+
}
|
|
3345
|
+
getSymbolsByName(name) {
|
|
3346
|
+
return this.inner.getSymbolsByName(name);
|
|
3347
|
+
}
|
|
3348
|
+
getSymbolsByNameCi(name) {
|
|
3349
|
+
return this.inner.getSymbolsByNameCi(name);
|
|
3350
|
+
}
|
|
3351
|
+
deleteSymbolsByFile(filePath) {
|
|
3352
|
+
return this.inner.deleteSymbolsByFile(filePath);
|
|
3353
|
+
}
|
|
3354
|
+
// ── Call Edge methods ────────────────────────────────────────────
|
|
3355
|
+
upsertCallEdge(edge) {
|
|
3356
|
+
this.inner.upsertCallEdge(edge);
|
|
3357
|
+
}
|
|
3358
|
+
upsertCallEdgesBatch(edges) {
|
|
3359
|
+
if (edges.length === 0) return;
|
|
3360
|
+
this.inner.upsertCallEdgesBatch(edges);
|
|
3361
|
+
}
|
|
3362
|
+
getCallers(targetName, branch) {
|
|
3363
|
+
return this.inner.getCallers(targetName, branch);
|
|
3364
|
+
}
|
|
3365
|
+
getCallersWithContext(targetName, branch) {
|
|
3366
|
+
return this.inner.getCallersWithContext(targetName, branch);
|
|
3367
|
+
}
|
|
3368
|
+
getCallees(symbolId, branch) {
|
|
3369
|
+
return this.inner.getCallees(symbolId, branch);
|
|
3370
|
+
}
|
|
3371
|
+
deleteCallEdgesByFile(filePath) {
|
|
3372
|
+
return this.inner.deleteCallEdgesByFile(filePath);
|
|
3373
|
+
}
|
|
3374
|
+
resolveCallEdge(edgeId, toSymbolId) {
|
|
3375
|
+
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
3376
|
+
}
|
|
3377
|
+
// ── Branch Symbol methods ────────────────────────────────────────
|
|
3378
|
+
addSymbolsToBranch(branch, symbolIds) {
|
|
3379
|
+
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
3380
|
+
}
|
|
3381
|
+
addSymbolsToBranchBatch(branch, symbolIds) {
|
|
3382
|
+
if (symbolIds.length === 0) return;
|
|
3383
|
+
this.inner.addSymbolsToBranchBatch(branch, symbolIds);
|
|
3384
|
+
}
|
|
3385
|
+
getBranchSymbolIds(branch) {
|
|
3386
|
+
return this.inner.getBranchSymbolIds(branch);
|
|
3387
|
+
}
|
|
3388
|
+
clearBranchSymbols(branch) {
|
|
3389
|
+
return this.inner.clearBranchSymbols(branch);
|
|
3390
|
+
}
|
|
3391
|
+
// ── GC methods for symbols/edges ─────────────────────────────────
|
|
3392
|
+
gcOrphanSymbols() {
|
|
3393
|
+
return this.inner.gcOrphanSymbols();
|
|
3394
|
+
}
|
|
3395
|
+
gcOrphanCallEdges() {
|
|
3396
|
+
return this.inner.gcOrphanCallEdges();
|
|
3397
|
+
}
|
|
3313
3398
|
};
|
|
3314
3399
|
|
|
3315
3400
|
// src/git/index.ts
|
|
@@ -3410,6 +3495,29 @@ function getBranchOrDefault(repoRoot) {
|
|
|
3410
3495
|
}
|
|
3411
3496
|
|
|
3412
3497
|
// src/indexer/index.ts
|
|
3498
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust"]);
|
|
3499
|
+
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
3500
|
+
"function_declaration",
|
|
3501
|
+
"function",
|
|
3502
|
+
"arrow_function",
|
|
3503
|
+
"method_definition",
|
|
3504
|
+
"class_declaration",
|
|
3505
|
+
"interface_declaration",
|
|
3506
|
+
"type_alias_declaration",
|
|
3507
|
+
"enum_declaration",
|
|
3508
|
+
"function_definition",
|
|
3509
|
+
"class_definition",
|
|
3510
|
+
"decorated_definition",
|
|
3511
|
+
"method_declaration",
|
|
3512
|
+
"type_declaration",
|
|
3513
|
+
"type_spec",
|
|
3514
|
+
"function_item",
|
|
3515
|
+
"impl_item",
|
|
3516
|
+
"struct_item",
|
|
3517
|
+
"enum_item",
|
|
3518
|
+
"trait_item",
|
|
3519
|
+
"mod_item"
|
|
3520
|
+
]);
|
|
3413
3521
|
function float32ArrayToBuffer(arr) {
|
|
3414
3522
|
const float32 = new Float32Array(arr);
|
|
3415
3523
|
return Buffer.from(float32.buffer);
|
|
@@ -3434,6 +3542,892 @@ function isRateLimitError(error) {
|
|
|
3434
3542
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
3435
3543
|
}
|
|
3436
3544
|
var INDEX_METADATA_VERSION = "1";
|
|
3545
|
+
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
3546
|
+
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
3547
|
+
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
3548
|
+
var rankingPathTokenCache = /* @__PURE__ */ new Map();
|
|
3549
|
+
var rankingTextTokenCache = /* @__PURE__ */ new Map();
|
|
3550
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3551
|
+
"the",
|
|
3552
|
+
"and",
|
|
3553
|
+
"for",
|
|
3554
|
+
"with",
|
|
3555
|
+
"from",
|
|
3556
|
+
"that",
|
|
3557
|
+
"this",
|
|
3558
|
+
"into",
|
|
3559
|
+
"using",
|
|
3560
|
+
"where",
|
|
3561
|
+
"what",
|
|
3562
|
+
"when",
|
|
3563
|
+
"why",
|
|
3564
|
+
"how",
|
|
3565
|
+
"are",
|
|
3566
|
+
"was",
|
|
3567
|
+
"were",
|
|
3568
|
+
"be",
|
|
3569
|
+
"been",
|
|
3570
|
+
"being",
|
|
3571
|
+
"find",
|
|
3572
|
+
"show",
|
|
3573
|
+
"get",
|
|
3574
|
+
"run",
|
|
3575
|
+
"use",
|
|
3576
|
+
"code",
|
|
3577
|
+
"function",
|
|
3578
|
+
"implementation",
|
|
3579
|
+
"retrieve",
|
|
3580
|
+
"results",
|
|
3581
|
+
"result",
|
|
3582
|
+
"search",
|
|
3583
|
+
"pipeline",
|
|
3584
|
+
"top",
|
|
3585
|
+
"in",
|
|
3586
|
+
"on",
|
|
3587
|
+
"of",
|
|
3588
|
+
"to",
|
|
3589
|
+
"by",
|
|
3590
|
+
"as",
|
|
3591
|
+
"or",
|
|
3592
|
+
"an",
|
|
3593
|
+
"a"
|
|
3594
|
+
]);
|
|
3595
|
+
var TEST_PATH_SEGMENTS = [
|
|
3596
|
+
"tests/",
|
|
3597
|
+
"__tests__/",
|
|
3598
|
+
"/test/",
|
|
3599
|
+
"fixtures/",
|
|
3600
|
+
"benchmark",
|
|
3601
|
+
"README",
|
|
3602
|
+
"ARCHITECTURE",
|
|
3603
|
+
"docs/"
|
|
3604
|
+
];
|
|
3605
|
+
var IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS = [
|
|
3606
|
+
"tests/",
|
|
3607
|
+
"__tests__/",
|
|
3608
|
+
"/test/",
|
|
3609
|
+
"fixtures/",
|
|
3610
|
+
"benchmark",
|
|
3611
|
+
"readme",
|
|
3612
|
+
"architecture",
|
|
3613
|
+
"docs/",
|
|
3614
|
+
"examples/",
|
|
3615
|
+
"example/",
|
|
3616
|
+
".github/",
|
|
3617
|
+
"/scripts/",
|
|
3618
|
+
"/migrations/",
|
|
3619
|
+
"/generated/"
|
|
3620
|
+
];
|
|
3621
|
+
var SOURCE_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3622
|
+
"implement",
|
|
3623
|
+
"implementation",
|
|
3624
|
+
"function",
|
|
3625
|
+
"method",
|
|
3626
|
+
"class",
|
|
3627
|
+
"logic",
|
|
3628
|
+
"algorithm",
|
|
3629
|
+
"pipeline",
|
|
3630
|
+
"indexer",
|
|
3631
|
+
"where"
|
|
3632
|
+
]);
|
|
3633
|
+
var DOC_TEST_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3634
|
+
"test",
|
|
3635
|
+
"tests",
|
|
3636
|
+
"fixture",
|
|
3637
|
+
"fixtures",
|
|
3638
|
+
"benchmark",
|
|
3639
|
+
"readme",
|
|
3640
|
+
"docs",
|
|
3641
|
+
"documentation"
|
|
3642
|
+
]);
|
|
3643
|
+
var DOC_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3644
|
+
"readme",
|
|
3645
|
+
"docs",
|
|
3646
|
+
"documentation",
|
|
3647
|
+
"guide",
|
|
3648
|
+
"usage"
|
|
3649
|
+
]);
|
|
3650
|
+
function setBoundedCache(cache, key, value) {
|
|
3651
|
+
if (cache.size >= RANKING_TOKEN_CACHE_LIMIT) {
|
|
3652
|
+
const oldest = cache.keys().next().value;
|
|
3653
|
+
if (oldest !== void 0) {
|
|
3654
|
+
cache.delete(oldest);
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
cache.set(key, value);
|
|
3658
|
+
}
|
|
3659
|
+
function tokenizeTextForRanking(text) {
|
|
3660
|
+
if (!text) {
|
|
3661
|
+
return /* @__PURE__ */ new Set();
|
|
3662
|
+
}
|
|
3663
|
+
const lowered = text.toLowerCase();
|
|
3664
|
+
const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
|
|
3665
|
+
if (cache) {
|
|
3666
|
+
return cache;
|
|
3667
|
+
}
|
|
3668
|
+
const tokens = new Set(
|
|
3669
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !STOPWORDS.has(token))
|
|
3670
|
+
);
|
|
3671
|
+
setBoundedCache(rankingQueryTokenCache, lowered, tokens);
|
|
3672
|
+
setBoundedCache(rankingTextTokenCache, lowered, tokens);
|
|
3673
|
+
return tokens;
|
|
3674
|
+
}
|
|
3675
|
+
function splitPathTokens(filePath) {
|
|
3676
|
+
const lowered = filePath.toLowerCase();
|
|
3677
|
+
const cache = rankingPathTokenCache.get(lowered);
|
|
3678
|
+
if (cache) {
|
|
3679
|
+
return cache;
|
|
3680
|
+
}
|
|
3681
|
+
const normalized = lowered.replace(/[^a-z0-9/._-]/g, " ").split(/[/._-]+/).filter((token) => token.length > 1);
|
|
3682
|
+
const tokens = new Set(normalized);
|
|
3683
|
+
setBoundedCache(rankingPathTokenCache, lowered, tokens);
|
|
3684
|
+
return tokens;
|
|
3685
|
+
}
|
|
3686
|
+
function splitNameTokens(name) {
|
|
3687
|
+
if (!name) {
|
|
3688
|
+
return /* @__PURE__ */ new Set();
|
|
3689
|
+
}
|
|
3690
|
+
const lowered = name.toLowerCase();
|
|
3691
|
+
const cache = rankingNameTokenCache.get(lowered);
|
|
3692
|
+
if (cache) {
|
|
3693
|
+
return cache;
|
|
3694
|
+
}
|
|
3695
|
+
const tokens = new Set(
|
|
3696
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1)
|
|
3697
|
+
);
|
|
3698
|
+
setBoundedCache(rankingNameTokenCache, lowered, tokens);
|
|
3699
|
+
return tokens;
|
|
3700
|
+
}
|
|
3701
|
+
function chunkTypeBoost(chunkType) {
|
|
3702
|
+
switch (chunkType) {
|
|
3703
|
+
case "function":
|
|
3704
|
+
case "function_declaration":
|
|
3705
|
+
case "method":
|
|
3706
|
+
case "method_definition":
|
|
3707
|
+
case "class":
|
|
3708
|
+
case "class_declaration":
|
|
3709
|
+
return 0.2;
|
|
3710
|
+
case "interface":
|
|
3711
|
+
case "type":
|
|
3712
|
+
case "enum":
|
|
3713
|
+
case "struct":
|
|
3714
|
+
case "impl":
|
|
3715
|
+
case "trait":
|
|
3716
|
+
case "module":
|
|
3717
|
+
return 0.1;
|
|
3718
|
+
default:
|
|
3719
|
+
return 0;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
function isTestOrDocPath(filePath) {
|
|
3723
|
+
return TEST_PATH_SEGMENTS.some((segment) => filePath.includes(segment));
|
|
3724
|
+
}
|
|
3725
|
+
function isLikelyImplementationPath(filePath) {
|
|
3726
|
+
const lowered = filePath.toLowerCase();
|
|
3727
|
+
if (IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS.some((segment) => lowered.includes(segment))) {
|
|
3728
|
+
return false;
|
|
3729
|
+
}
|
|
3730
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
3731
|
+
if (["md", "mdx", "txt", "rst", "adoc", "snap", "json", "yaml", "yml", "lock"].includes(ext)) {
|
|
3732
|
+
return false;
|
|
3733
|
+
}
|
|
3734
|
+
return true;
|
|
3735
|
+
}
|
|
3736
|
+
function classifyQueryIntent(tokens) {
|
|
3737
|
+
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
3738
|
+
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
3739
|
+
return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
|
|
3740
|
+
}
|
|
3741
|
+
function classifyQueryIntentRaw(query) {
|
|
3742
|
+
const lowerQuery = query.toLowerCase();
|
|
3743
|
+
const docTestRawHits = Array.from(DOC_TEST_INTENT_HINTS).filter(
|
|
3744
|
+
(hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)
|
|
3745
|
+
).length;
|
|
3746
|
+
const sourceRawHits = [
|
|
3747
|
+
"implement",
|
|
3748
|
+
"implementation",
|
|
3749
|
+
"implements",
|
|
3750
|
+
"function",
|
|
3751
|
+
"method",
|
|
3752
|
+
"class",
|
|
3753
|
+
"logic",
|
|
3754
|
+
"algorithm",
|
|
3755
|
+
"pipeline",
|
|
3756
|
+
"indexer"
|
|
3757
|
+
].filter((hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)).length;
|
|
3758
|
+
if (docTestRawHits > sourceRawHits) {
|
|
3759
|
+
return "doc_test";
|
|
3760
|
+
}
|
|
3761
|
+
if (sourceRawHits > docTestRawHits) {
|
|
3762
|
+
return "source";
|
|
3763
|
+
}
|
|
3764
|
+
const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
|
|
3765
|
+
const hasIdentifierHints = extractIdentifierHints(query).length > 0;
|
|
3766
|
+
if (hasWhereIsPattern && hasIdentifierHints && docTestRawHits === 0) {
|
|
3767
|
+
return "source";
|
|
3768
|
+
}
|
|
3769
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
3770
|
+
return classifyQueryIntent(queryTokens);
|
|
3771
|
+
}
|
|
3772
|
+
function classifyDocIntent(tokens) {
|
|
3773
|
+
const docHits = tokens.filter((t) => DOC_INTENT_HINTS.has(t)).length;
|
|
3774
|
+
const testHits = tokens.filter((t) => ["test", "tests", "fixture", "fixtures", "benchmark"].includes(t)).length;
|
|
3775
|
+
if (docHits > 0 && testHits === 0) return "docs";
|
|
3776
|
+
if (testHits > 0 && docHits === 0) return "test";
|
|
3777
|
+
if (testHits > 0 || docHits > 0) return "mixed";
|
|
3778
|
+
return "none";
|
|
3779
|
+
}
|
|
3780
|
+
function isImplementationChunkType(chunkType) {
|
|
3781
|
+
return [
|
|
3782
|
+
"export_statement",
|
|
3783
|
+
"function",
|
|
3784
|
+
"function_declaration",
|
|
3785
|
+
"method",
|
|
3786
|
+
"method_definition",
|
|
3787
|
+
"class",
|
|
3788
|
+
"class_declaration",
|
|
3789
|
+
"interface",
|
|
3790
|
+
"type",
|
|
3791
|
+
"enum",
|
|
3792
|
+
"module"
|
|
3793
|
+
].includes(chunkType);
|
|
3794
|
+
}
|
|
3795
|
+
function extractIdentifierHints(query) {
|
|
3796
|
+
const identifiers = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3797
|
+
return identifiers.filter((id) => id.length >= 3).filter((id) => {
|
|
3798
|
+
const lower = id.toLowerCase();
|
|
3799
|
+
if (STOPWORDS.has(lower)) return false;
|
|
3800
|
+
return /[A-Z]/.test(id) || id.includes("_") || id.endsWith("Results") || id.endsWith("Result");
|
|
3801
|
+
}).map((id) => id.toLowerCase());
|
|
3802
|
+
}
|
|
3803
|
+
function extractCodeTermHints(query) {
|
|
3804
|
+
const terms = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3805
|
+
return terms.map((term) => term.toLowerCase()).filter((term) => term.length >= 3).filter((term) => !STOPWORDS.has(term));
|
|
3806
|
+
}
|
|
3807
|
+
function normalizeIdentifierVariants(identifier) {
|
|
3808
|
+
const lower = identifier.toLowerCase();
|
|
3809
|
+
const compact = lower.replace(/[^a-z0-9]/g, "");
|
|
3810
|
+
const snake = compact.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
3811
|
+
const kebab = snake.replace(/_/g, "-");
|
|
3812
|
+
const variants = [lower, compact, snake, kebab].filter((value) => value.length > 0);
|
|
3813
|
+
return Array.from(new Set(variants));
|
|
3814
|
+
}
|
|
3815
|
+
function scoreIdentifierMatch(name, filePath, hints) {
|
|
3816
|
+
const nameLower = (name ?? "").toLowerCase();
|
|
3817
|
+
const pathLower = filePath.toLowerCase();
|
|
3818
|
+
let best = 0;
|
|
3819
|
+
for (const hint of hints) {
|
|
3820
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3821
|
+
for (const variant of variants) {
|
|
3822
|
+
if (nameLower === variant) {
|
|
3823
|
+
best = Math.max(best, 1);
|
|
3824
|
+
} else if (nameLower.includes(variant)) {
|
|
3825
|
+
best = Math.max(best, 0.8);
|
|
3826
|
+
} else if (pathLower.includes(variant)) {
|
|
3827
|
+
best = Math.max(best, 0.6);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
return best;
|
|
3832
|
+
}
|
|
3833
|
+
function extractPrimaryIdentifierQueryHint(query) {
|
|
3834
|
+
const identifiers = extractIdentifierHints(query);
|
|
3835
|
+
if (identifiers.length > 0) {
|
|
3836
|
+
return identifiers[0] ?? null;
|
|
3837
|
+
}
|
|
3838
|
+
const codeTerms = extractCodeTermHints(query);
|
|
3839
|
+
const best = codeTerms.find((term) => term.length >= 6);
|
|
3840
|
+
return best ?? null;
|
|
3841
|
+
}
|
|
3842
|
+
var FILE_PATH_HINT_EXTENSIONS = [
|
|
3843
|
+
"ts",
|
|
3844
|
+
"tsx",
|
|
3845
|
+
"js",
|
|
3846
|
+
"jsx",
|
|
3847
|
+
"mjs",
|
|
3848
|
+
"cjs",
|
|
3849
|
+
"mts",
|
|
3850
|
+
"cts",
|
|
3851
|
+
"py",
|
|
3852
|
+
"rs",
|
|
3853
|
+
"go",
|
|
3854
|
+
"java",
|
|
3855
|
+
"kt",
|
|
3856
|
+
"kts",
|
|
3857
|
+
"swift",
|
|
3858
|
+
"rb",
|
|
3859
|
+
"php",
|
|
3860
|
+
"c",
|
|
3861
|
+
"h",
|
|
3862
|
+
"cc",
|
|
3863
|
+
"cpp",
|
|
3864
|
+
"cxx",
|
|
3865
|
+
"hpp",
|
|
3866
|
+
"cs",
|
|
3867
|
+
"scala",
|
|
3868
|
+
"lua",
|
|
3869
|
+
"sh",
|
|
3870
|
+
"bash",
|
|
3871
|
+
"zsh",
|
|
3872
|
+
"json",
|
|
3873
|
+
"yaml",
|
|
3874
|
+
"yml",
|
|
3875
|
+
"toml"
|
|
3876
|
+
];
|
|
3877
|
+
var FILE_PATH_HINT_SUFFIX_REGEX = new RegExp(
|
|
3878
|
+
"\\s+\\bin\\s+[\"'`]?((?:\\.\\/)?(?:[A-Za-z0-9._-]+\\/)+[A-Za-z0-9._-]+\\.(?:" + FILE_PATH_HINT_EXTENSIONS.join("|") + "))[\"'`]?[\\])}>.,;!?]*\\s*$",
|
|
3879
|
+
"i"
|
|
3880
|
+
);
|
|
3881
|
+
function normalizeFilePathForHintMatch(filePath) {
|
|
3882
|
+
return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
3883
|
+
}
|
|
3884
|
+
function pathMatchesHint(filePath, hint) {
|
|
3885
|
+
const normalizedPath = normalizeFilePathForHintMatch(filePath);
|
|
3886
|
+
const normalizedHint = normalizeFilePathForHintMatch(hint);
|
|
3887
|
+
return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
|
|
3888
|
+
}
|
|
3889
|
+
function extractFilePathHint(query) {
|
|
3890
|
+
const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
|
|
3891
|
+
const rawPath = match?.[1];
|
|
3892
|
+
if (!rawPath) {
|
|
3893
|
+
return null;
|
|
3894
|
+
}
|
|
3895
|
+
return rawPath.replace(/^\.\//, "");
|
|
3896
|
+
}
|
|
3897
|
+
function stripFilePathHint(query) {
|
|
3898
|
+
const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
|
|
3899
|
+
return stripped.length > 0 ? stripped : query;
|
|
3900
|
+
}
|
|
3901
|
+
function buildDeterministicIdentifierPass(query, candidates, limit) {
|
|
3902
|
+
if (classifyQueryIntentRaw(query) !== "source") {
|
|
3903
|
+
return [];
|
|
3904
|
+
}
|
|
3905
|
+
const primary = extractPrimaryIdentifierQueryHint(query);
|
|
3906
|
+
if (!primary) {
|
|
3907
|
+
return [];
|
|
3908
|
+
}
|
|
3909
|
+
const filePathHint = extractFilePathHint(query);
|
|
3910
|
+
const primaryVariants = normalizeIdentifierVariants(primary);
|
|
3911
|
+
const hints = [primary, ...extractIdentifierHints(query), ...extractCodeTermHints(query)].map((value) => value.toLowerCase()).filter((value, idx, arr) => value.length >= 3 && arr.indexOf(value) === idx).slice(0, 8);
|
|
3912
|
+
const deterministic = candidates.filter(
|
|
3913
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
3914
|
+
).map((candidate) => {
|
|
3915
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
3916
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
3917
|
+
let maxMatch = 0;
|
|
3918
|
+
const nameMatchesPrimary = primaryVariants.some(
|
|
3919
|
+
(variant) => nameLower === variant || nameLower.replace(/[^a-z0-9]/g, "") === variant.replace(/[^a-z0-9]/g, "")
|
|
3920
|
+
);
|
|
3921
|
+
const pathMatchesFileHint = filePathHint ? pathMatchesHint(candidate.metadata.filePath, filePathHint) : false;
|
|
3922
|
+
for (const hint of hints) {
|
|
3923
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3924
|
+
for (const variant of variants) {
|
|
3925
|
+
if (nameLower === variant) {
|
|
3926
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
3927
|
+
} else if (nameLower.includes(variant)) {
|
|
3928
|
+
maxMatch = Math.max(maxMatch, 0.85);
|
|
3929
|
+
} else if (pathLower.includes(variant)) {
|
|
3930
|
+
maxMatch = Math.max(maxMatch, 0.7);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
if (pathMatchesFileHint && nameMatchesPrimary) {
|
|
3935
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
3936
|
+
}
|
|
3937
|
+
return {
|
|
3938
|
+
candidate,
|
|
3939
|
+
maxMatch,
|
|
3940
|
+
pathMatchesFileHint,
|
|
3941
|
+
nameMatchesPrimary
|
|
3942
|
+
};
|
|
3943
|
+
}).filter((entry) => entry.maxMatch >= 0.7).sort((a, b) => {
|
|
3944
|
+
const aAnchored = a.pathMatchesFileHint && a.nameMatchesPrimary ? 1 : 0;
|
|
3945
|
+
const bAnchored = b.pathMatchesFileHint && b.nameMatchesPrimary ? 1 : 0;
|
|
3946
|
+
if (aAnchored !== bAnchored) return bAnchored - aAnchored;
|
|
3947
|
+
if (b.maxMatch !== a.maxMatch) return b.maxMatch - a.maxMatch;
|
|
3948
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
3949
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
3950
|
+
}).slice(0, Math.max(limit * 2, 12));
|
|
3951
|
+
return deterministic.map((entry) => ({
|
|
3952
|
+
id: entry.candidate.id,
|
|
3953
|
+
score: entry.pathMatchesFileHint && entry.nameMatchesPrimary ? 0.995 : Math.min(1, 0.9 + entry.maxMatch * 0.09),
|
|
3954
|
+
metadata: entry.candidate.metadata
|
|
3955
|
+
}));
|
|
3956
|
+
}
|
|
3957
|
+
function fuseResultsWeighted(semanticResults, keywordResults, keywordWeight, limit) {
|
|
3958
|
+
const semanticWeight = 1 - keywordWeight;
|
|
3959
|
+
const fusedScores = /* @__PURE__ */ new Map();
|
|
3960
|
+
for (const r of semanticResults) {
|
|
3961
|
+
fusedScores.set(r.id, {
|
|
3962
|
+
score: r.score * semanticWeight,
|
|
3963
|
+
metadata: r.metadata
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
for (const r of keywordResults) {
|
|
3967
|
+
const existing = fusedScores.get(r.id);
|
|
3968
|
+
if (existing) {
|
|
3969
|
+
existing.score += r.score * keywordWeight;
|
|
3970
|
+
} else {
|
|
3971
|
+
fusedScores.set(r.id, {
|
|
3972
|
+
score: r.score * keywordWeight,
|
|
3973
|
+
metadata: r.metadata
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
3978
|
+
id,
|
|
3979
|
+
score: data.score,
|
|
3980
|
+
metadata: data.metadata
|
|
3981
|
+
}));
|
|
3982
|
+
results.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
3983
|
+
return results.slice(0, limit);
|
|
3984
|
+
}
|
|
3985
|
+
function fuseResultsRrf(semanticResults, keywordResults, rrfK, limit) {
|
|
3986
|
+
const maxPossibleRaw = 2 / (rrfK + 1);
|
|
3987
|
+
const rankByIdSemantic = /* @__PURE__ */ new Map();
|
|
3988
|
+
const rankByIdKeyword = /* @__PURE__ */ new Map();
|
|
3989
|
+
const metadataById = /* @__PURE__ */ new Map();
|
|
3990
|
+
semanticResults.forEach((result, index) => {
|
|
3991
|
+
rankByIdSemantic.set(result.id, index + 1);
|
|
3992
|
+
metadataById.set(result.id, result.metadata);
|
|
3993
|
+
});
|
|
3994
|
+
keywordResults.forEach((result, index) => {
|
|
3995
|
+
rankByIdKeyword.set(result.id, index + 1);
|
|
3996
|
+
if (!metadataById.has(result.id)) {
|
|
3997
|
+
metadataById.set(result.id, result.metadata);
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
const allIds = /* @__PURE__ */ new Set([...rankByIdSemantic.keys(), ...rankByIdKeyword.keys()]);
|
|
4001
|
+
const fused = [];
|
|
4002
|
+
for (const id of allIds) {
|
|
4003
|
+
const semanticRank = rankByIdSemantic.get(id);
|
|
4004
|
+
const keywordRank = rankByIdKeyword.get(id);
|
|
4005
|
+
const semanticScore = semanticRank ? 1 / (rrfK + semanticRank) : 0;
|
|
4006
|
+
const keywordScore = keywordRank ? 1 / (rrfK + keywordRank) : 0;
|
|
4007
|
+
const metadata = metadataById.get(id);
|
|
4008
|
+
if (!metadata) continue;
|
|
4009
|
+
fused.push({
|
|
4010
|
+
id,
|
|
4011
|
+
score: maxPossibleRaw > 0 ? (semanticScore + keywordScore) / maxPossibleRaw : 0,
|
|
4012
|
+
metadata
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
4015
|
+
fused.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4016
|
+
return fused.slice(0, limit);
|
|
4017
|
+
}
|
|
4018
|
+
function rerankResults(query, candidates, rerankTopN, options) {
|
|
4019
|
+
if (rerankTopN <= 0 || candidates.length <= 1) {
|
|
4020
|
+
return candidates;
|
|
4021
|
+
}
|
|
4022
|
+
const topN = Math.min(rerankTopN, candidates.length);
|
|
4023
|
+
const queryTokens = tokenizeTextForRanking(query);
|
|
4024
|
+
if (queryTokens.size === 0) {
|
|
4025
|
+
return candidates;
|
|
4026
|
+
}
|
|
4027
|
+
const queryTokenList = Array.from(queryTokens);
|
|
4028
|
+
const intent = classifyQueryIntentRaw(query);
|
|
4029
|
+
const docIntent = classifyDocIntent(queryTokenList);
|
|
4030
|
+
const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
|
|
4031
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4032
|
+
const head = candidates.slice(0, topN).map((candidate, idx) => {
|
|
4033
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4034
|
+
const nameTokens = splitNameTokens(candidate.metadata.name ?? "");
|
|
4035
|
+
const chunkTypeTokens = tokenizeTextForRanking(candidate.metadata.chunkType);
|
|
4036
|
+
let exactOrPrefixNameHits = 0;
|
|
4037
|
+
let pathOverlap = 0;
|
|
4038
|
+
let chunkTypeHits = 0;
|
|
4039
|
+
for (const token of queryTokenList) {
|
|
4040
|
+
if (nameTokens.has(token)) {
|
|
4041
|
+
exactOrPrefixNameHits += 1;
|
|
4042
|
+
} else {
|
|
4043
|
+
for (const nameToken of nameTokens) {
|
|
4044
|
+
if (nameToken.startsWith(token) || token.startsWith(nameToken)) {
|
|
4045
|
+
exactOrPrefixNameHits += 1;
|
|
4046
|
+
break;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
if (pathTokens.has(token)) {
|
|
4051
|
+
pathOverlap += 1;
|
|
4052
|
+
}
|
|
4053
|
+
if (chunkTypeTokens.has(token)) {
|
|
4054
|
+
chunkTypeHits += 1;
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
const likelyTestOrDoc = isTestOrDocPath(candidate.metadata.filePath);
|
|
4058
|
+
const lowerPath = candidate.metadata.filePath.toLowerCase();
|
|
4059
|
+
const lowerName = (candidate.metadata.name ?? "").toLowerCase();
|
|
4060
|
+
const hasIdentifierMatch = identifierHints.some((id) => lowerPath.includes(id) || lowerName.includes(id));
|
|
4061
|
+
const implementationPathBoost = preferSourcePaths && isLikelyImplementationPath(candidate.metadata.filePath) ? 0.08 : 0;
|
|
4062
|
+
const isReadmePath = candidate.metadata.filePath.toLowerCase().includes("readme");
|
|
4063
|
+
const testDocPenalty = preferSourcePaths && likelyTestOrDoc ? 0.12 : 0;
|
|
4064
|
+
const readmeDocBoost = !preferSourcePaths && isReadmePath ? 0.08 : 0;
|
|
4065
|
+
const identifierBoost = hasIdentifierMatch ? 0.12 : 0;
|
|
4066
|
+
const tokenCoverage = queryTokenList.length > 0 ? (exactOrPrefixNameHits + pathOverlap + chunkTypeHits) / queryTokenList.length : 0;
|
|
4067
|
+
const coverageBoost = Math.min(0.12, tokenCoverage * 0.06);
|
|
4068
|
+
const deterministicBoost = exactOrPrefixNameHits * 0.08 + pathOverlap * 0.03 + chunkTypeHits * 0.02 + coverageBoost + identifierBoost + implementationPathBoost - testDocPenalty + readmeDocBoost + chunkTypeBoost(candidate.metadata.chunkType);
|
|
4069
|
+
return {
|
|
4070
|
+
candidate,
|
|
4071
|
+
boostedScore: candidate.score + deterministicBoost,
|
|
4072
|
+
originalIndex: idx,
|
|
4073
|
+
hasIdentifierMatch,
|
|
4074
|
+
implementationChunk: isImplementationChunkType(candidate.metadata.chunkType),
|
|
4075
|
+
isLikelyImplementationPath: isLikelyImplementationPath(candidate.metadata.filePath),
|
|
4076
|
+
isTestOrDocPath: likelyTestOrDoc,
|
|
4077
|
+
isReadmePath
|
|
4078
|
+
};
|
|
4079
|
+
});
|
|
4080
|
+
head.sort((a, b) => {
|
|
4081
|
+
if (b.boostedScore !== a.boostedScore) return b.boostedScore - a.boostedScore;
|
|
4082
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4083
|
+
if (a.originalIndex !== b.originalIndex) return a.originalIndex - b.originalIndex;
|
|
4084
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4085
|
+
});
|
|
4086
|
+
if (preferSourcePaths) {
|
|
4087
|
+
head.sort((a, b) => {
|
|
4088
|
+
const aId = a.hasIdentifierMatch ? 1 : 0;
|
|
4089
|
+
const bId = b.hasIdentifierMatch ? 1 : 0;
|
|
4090
|
+
if (aId !== bId) return bId - aId;
|
|
4091
|
+
const aImpl = a.implementationChunk ? 1 : 0;
|
|
4092
|
+
const bImpl = b.implementationChunk ? 1 : 0;
|
|
4093
|
+
if (aImpl !== bImpl) return bImpl - aImpl;
|
|
4094
|
+
const aImplementationPath = a.isLikelyImplementationPath ? 1 : 0;
|
|
4095
|
+
const bImplementationPath = b.isLikelyImplementationPath ? 1 : 0;
|
|
4096
|
+
if (aImplementationPath !== bImplementationPath) return bImplementationPath - aImplementationPath;
|
|
4097
|
+
const aTestDoc = a.isTestOrDocPath ? 1 : 0;
|
|
4098
|
+
const bTestDoc = b.isTestOrDocPath ? 1 : 0;
|
|
4099
|
+
if (aTestDoc !== bTestDoc) return aTestDoc - bTestDoc;
|
|
4100
|
+
return 0;
|
|
4101
|
+
});
|
|
4102
|
+
} else if (docIntent === "docs") {
|
|
4103
|
+
head.sort((a, b) => {
|
|
4104
|
+
const aReadme = a.isReadmePath ? 1 : 0;
|
|
4105
|
+
const bReadme = b.isReadmePath ? 1 : 0;
|
|
4106
|
+
if (aReadme !== bReadme) return bReadme - aReadme;
|
|
4107
|
+
return 0;
|
|
4108
|
+
});
|
|
4109
|
+
}
|
|
4110
|
+
const tail = candidates.slice(topN);
|
|
4111
|
+
return [...head.map((entry) => entry.candidate), ...tail];
|
|
4112
|
+
}
|
|
4113
|
+
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4114
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4115
|
+
const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
|
|
4116
|
+
const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
|
|
4117
|
+
const rerankPool = fused.slice(0, rerankPoolLimit);
|
|
4118
|
+
const intent = classifyQueryIntentRaw(query);
|
|
4119
|
+
return rerankResults(query, rerankPool, options.rerankTopN, {
|
|
4120
|
+
prioritizeSourcePaths: intent === "source"
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
4124
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4125
|
+
const bounded = semanticResults.slice(0, overfetchLimit);
|
|
4126
|
+
return rerankResults(query, bounded, options.rerankTopN, {
|
|
4127
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds) {
|
|
4131
|
+
if (combined.length === 0) {
|
|
4132
|
+
return combined;
|
|
4133
|
+
}
|
|
4134
|
+
if (classifyQueryIntentRaw(query) !== "source") {
|
|
4135
|
+
return combined;
|
|
4136
|
+
}
|
|
4137
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4138
|
+
if (identifierHints.length === 0) {
|
|
4139
|
+
return combined;
|
|
4140
|
+
}
|
|
4141
|
+
const combinedById = new Map(combined.map((candidate) => [candidate.id, candidate]));
|
|
4142
|
+
const candidateUnion = /* @__PURE__ */ new Map();
|
|
4143
|
+
for (const candidate of semanticCandidates) {
|
|
4144
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4145
|
+
}
|
|
4146
|
+
for (const candidate of keywordCandidates) {
|
|
4147
|
+
if (!candidateUnion.has(candidate.id)) {
|
|
4148
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
if (database) {
|
|
4152
|
+
for (const identifier of identifierHints) {
|
|
4153
|
+
const symbols = database.getSymbolsByName(identifier);
|
|
4154
|
+
for (const symbol of symbols) {
|
|
4155
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4156
|
+
for (const chunk of chunks) {
|
|
4157
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4158
|
+
continue;
|
|
4159
|
+
}
|
|
4160
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4161
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4162
|
+
continue;
|
|
4163
|
+
}
|
|
4164
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4168
|
+
continue;
|
|
4169
|
+
}
|
|
4170
|
+
const existing = combinedById.get(chunk.chunkId) ?? candidateUnion.get(chunk.chunkId);
|
|
4171
|
+
const metadata = existing?.metadata ?? {
|
|
4172
|
+
filePath: chunk.filePath,
|
|
4173
|
+
startLine: chunk.startLine,
|
|
4174
|
+
endLine: chunk.endLine,
|
|
4175
|
+
chunkType,
|
|
4176
|
+
name: chunk.name ?? void 0,
|
|
4177
|
+
language: chunk.language,
|
|
4178
|
+
hash: chunk.contentHash
|
|
4179
|
+
};
|
|
4180
|
+
const baselineScore = existing?.score ?? 0.5;
|
|
4181
|
+
candidateUnion.set(chunk.chunkId, {
|
|
4182
|
+
id: chunk.chunkId,
|
|
4183
|
+
score: Math.min(1, baselineScore + 0.5),
|
|
4184
|
+
metadata
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
const promoted = [];
|
|
4191
|
+
for (const candidate of candidateUnion.values()) {
|
|
4192
|
+
const filePathLower = candidate.metadata.filePath.toLowerCase();
|
|
4193
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4194
|
+
const exactIdentifierMatch = identifierHints.some((hint) => nameLower === hint);
|
|
4195
|
+
const hasIdentifierMatch = exactIdentifierMatch || identifierHints.some(
|
|
4196
|
+
(hint) => nameLower.includes(hint) || filePathLower.includes(hint)
|
|
4197
|
+
);
|
|
4198
|
+
if (!hasIdentifierMatch) {
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
if (!isImplementationChunkType(candidate.metadata.chunkType)) {
|
|
4202
|
+
continue;
|
|
4203
|
+
}
|
|
4204
|
+
if (!isLikelyImplementationPath(candidate.metadata.filePath)) {
|
|
4205
|
+
continue;
|
|
4206
|
+
}
|
|
4207
|
+
const existing = combinedById.get(candidate.id) ?? candidate;
|
|
4208
|
+
const rescueBoost = exactIdentifierMatch ? 0.45 : 0.25;
|
|
4209
|
+
const boostedScore = Math.min(1, Math.max(existing.score, candidate.score) + rescueBoost);
|
|
4210
|
+
promoted.push({
|
|
4211
|
+
id: existing.id,
|
|
4212
|
+
score: boostedScore,
|
|
4213
|
+
metadata: existing.metadata
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
if (promoted.length === 0) {
|
|
4217
|
+
return combined;
|
|
4218
|
+
}
|
|
4219
|
+
promoted.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4220
|
+
const promotedIds = new Set(promoted.map((candidate) => candidate.id));
|
|
4221
|
+
const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
|
|
4222
|
+
return [...promoted, ...remainder];
|
|
4223
|
+
}
|
|
4224
|
+
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates) {
|
|
4225
|
+
if (classifyQueryIntentRaw(query) !== "source") {
|
|
4226
|
+
return [];
|
|
4227
|
+
}
|
|
4228
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4229
|
+
const codeTermHints = extractCodeTermHints(query);
|
|
4230
|
+
if (identifierHints.length === 0 && codeTermHints.length === 0) {
|
|
4231
|
+
return [];
|
|
4232
|
+
}
|
|
4233
|
+
const symbolCandidates = /* @__PURE__ */ new Map();
|
|
4234
|
+
const filePathHint = extractFilePathHint(query);
|
|
4235
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4236
|
+
const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
|
|
4237
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4241
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4242
|
+
return;
|
|
4243
|
+
}
|
|
4244
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
const nameLower = (chunk.name ?? "").toLowerCase();
|
|
4248
|
+
const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
|
|
4249
|
+
const base = baseScore ?? (exactName ? 0.99 : 0.88);
|
|
4250
|
+
const existing = symbolCandidates.get(chunk.chunkId);
|
|
4251
|
+
if (!existing || base > existing.score) {
|
|
4252
|
+
symbolCandidates.set(chunk.chunkId, {
|
|
4253
|
+
id: chunk.chunkId,
|
|
4254
|
+
score: base,
|
|
4255
|
+
metadata: {
|
|
4256
|
+
filePath: chunk.filePath,
|
|
4257
|
+
startLine: chunk.startLine,
|
|
4258
|
+
endLine: chunk.endLine,
|
|
4259
|
+
chunkType,
|
|
4260
|
+
name: chunk.name ?? void 0,
|
|
4261
|
+
language: chunk.language,
|
|
4262
|
+
hash: chunk.contentHash
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
};
|
|
4267
|
+
const normalizedHints = identifierHints.flatMap((hint) => [
|
|
4268
|
+
hint,
|
|
4269
|
+
hint.replace(/_/g, ""),
|
|
4270
|
+
hint.replace(/_/g, "-")
|
|
4271
|
+
]).filter((hint, idx, arr) => hint.length >= 3 && arr.indexOf(hint) === idx).slice(0, 6);
|
|
4272
|
+
for (const identifier of normalizedHints) {
|
|
4273
|
+
const symbols = [
|
|
4274
|
+
...database.getSymbolsByName(identifier),
|
|
4275
|
+
...database.getSymbolsByNameCi(identifier)
|
|
4276
|
+
];
|
|
4277
|
+
const chunksByName = [
|
|
4278
|
+
...database.getChunksByName(identifier),
|
|
4279
|
+
...database.getChunksByNameCi(identifier)
|
|
4280
|
+
];
|
|
4281
|
+
const normalizedIdentifier = identifier.replace(/_/g, "");
|
|
4282
|
+
const dedupSymbols = /* @__PURE__ */ new Map();
|
|
4283
|
+
for (const symbol of symbols) {
|
|
4284
|
+
dedupSymbols.set(symbol.id, symbol);
|
|
4285
|
+
}
|
|
4286
|
+
for (const symbol of dedupSymbols.values()) {
|
|
4287
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4288
|
+
for (const chunk of chunks) {
|
|
4289
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4290
|
+
continue;
|
|
4291
|
+
}
|
|
4292
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
const dedupChunksByName = /* @__PURE__ */ new Map();
|
|
4296
|
+
for (const chunk of chunksByName) {
|
|
4297
|
+
dedupChunksByName.set(chunk.chunkId, chunk);
|
|
4298
|
+
}
|
|
4299
|
+
for (const chunk of dedupChunksByName.values()) {
|
|
4300
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
if (filePathHint && primaryHint) {
|
|
4304
|
+
const primaryChunks = [
|
|
4305
|
+
...database.getChunksByName(primaryHint),
|
|
4306
|
+
...database.getChunksByNameCi(primaryHint)
|
|
4307
|
+
];
|
|
4308
|
+
const dedupPrimaryChunks = /* @__PURE__ */ new Map();
|
|
4309
|
+
for (const chunk of primaryChunks) {
|
|
4310
|
+
dedupPrimaryChunks.set(chunk.chunkId, chunk);
|
|
4311
|
+
}
|
|
4312
|
+
for (const chunk of dedupPrimaryChunks.values()) {
|
|
4313
|
+
if (!pathMatchesHint(chunk.filePath, filePathHint)) {
|
|
4314
|
+
continue;
|
|
4315
|
+
}
|
|
4316
|
+
const normalizedPrimary = primaryHint.replace(/_/g, "");
|
|
4317
|
+
upsertChunkCandidate(chunk, primaryHint, normalizedPrimary, 1);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4321
|
+
if (ranked.length === 0) {
|
|
4322
|
+
const implementationFallback = fallbackCandidates.filter(
|
|
4323
|
+
(candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath(candidate.metadata.filePath)
|
|
4324
|
+
);
|
|
4325
|
+
for (const candidate of implementationFallback) {
|
|
4326
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4327
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
4328
|
+
const exactHintMatch = normalizedHints.some((hint) => nameLower === hint || nameLower.replace(/_/g, "") === hint.replace(/_/g, ""));
|
|
4329
|
+
const tokenizedName = tokenizeTextForRanking(nameLower);
|
|
4330
|
+
const tokenHits = codeTermHints.filter((term) => tokenizedName.has(term) || pathLower.includes(term)).length;
|
|
4331
|
+
if (!exactHintMatch && tokenHits === 0) {
|
|
4332
|
+
continue;
|
|
4333
|
+
}
|
|
4334
|
+
const laneScore = exactHintMatch ? Math.min(1, Math.max(candidate.score, 0.97)) : Math.min(0.95, Math.max(candidate.score, 0.82 + tokenHits * 0.03));
|
|
4335
|
+
symbolCandidates.set(candidate.id, {
|
|
4336
|
+
id: candidate.id,
|
|
4337
|
+
score: laneScore,
|
|
4338
|
+
metadata: candidate.metadata
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
4341
|
+
if (symbolCandidates.size === 0) {
|
|
4342
|
+
const queryTokenSet = tokenizeTextForRanking(query);
|
|
4343
|
+
const rankedFallback = implementationFallback.map((candidate) => {
|
|
4344
|
+
const nameTokens = tokenizeTextForRanking(candidate.metadata.name ?? "");
|
|
4345
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4346
|
+
let overlap = 0;
|
|
4347
|
+
for (const token of queryTokenSet) {
|
|
4348
|
+
if (nameTokens.has(token) || pathTokens.has(token)) {
|
|
4349
|
+
overlap += 1;
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
const overlapScore = queryTokenSet.size > 0 ? overlap / queryTokenSet.size : 0;
|
|
4353
|
+
return {
|
|
4354
|
+
candidate,
|
|
4355
|
+
overlapScore
|
|
4356
|
+
};
|
|
4357
|
+
}).filter((entry) => entry.overlapScore > 0).sort((a, b) => b.overlapScore - a.overlapScore || b.candidate.score - a.candidate.score).slice(0, Math.max(limit, 3));
|
|
4358
|
+
for (const entry of rankedFallback) {
|
|
4359
|
+
symbolCandidates.set(entry.candidate.id, {
|
|
4360
|
+
id: entry.candidate.id,
|
|
4361
|
+
score: Math.min(0.94, Math.max(entry.candidate.score, 0.8 + entry.overlapScore * 0.1)),
|
|
4362
|
+
metadata: entry.candidate.metadata
|
|
4363
|
+
});
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4368
|
+
return withFallback.slice(0, Math.max(limit * 2, limit));
|
|
4369
|
+
}
|
|
4370
|
+
function buildIdentifierDefinitionLane(query, candidates, limit) {
|
|
4371
|
+
if (classifyQueryIntentRaw(query) !== "source") {
|
|
4372
|
+
return [];
|
|
4373
|
+
}
|
|
4374
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4375
|
+
if (!primaryHint) {
|
|
4376
|
+
return [];
|
|
4377
|
+
}
|
|
4378
|
+
const hints = [primaryHint, ...extractIdentifierHints(query), ...extractCodeTermHints(query)].slice(0, 8);
|
|
4379
|
+
const scored = candidates.filter(
|
|
4380
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
4381
|
+
).map((candidate) => {
|
|
4382
|
+
const matchScore = scoreIdentifierMatch(candidate.metadata.name, candidate.metadata.filePath, hints);
|
|
4383
|
+
return {
|
|
4384
|
+
candidate,
|
|
4385
|
+
matchScore
|
|
4386
|
+
};
|
|
4387
|
+
}).filter((entry) => entry.matchScore > 0).sort((a, b) => {
|
|
4388
|
+
if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
|
|
4389
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4390
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4391
|
+
}).slice(0, Math.max(limit * 2, 10));
|
|
4392
|
+
return scored.map((entry) => ({
|
|
4393
|
+
id: entry.candidate.id,
|
|
4394
|
+
score: Math.min(1, 0.9 + entry.matchScore * 0.09),
|
|
4395
|
+
metadata: entry.candidate.metadata
|
|
4396
|
+
}));
|
|
4397
|
+
}
|
|
4398
|
+
function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
4399
|
+
if (symbolLane.length === 0) {
|
|
4400
|
+
return hybridLane.slice(0, limit);
|
|
4401
|
+
}
|
|
4402
|
+
const out = [];
|
|
4403
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4404
|
+
for (const candidate of symbolLane) {
|
|
4405
|
+
if (seen.has(candidate.id)) continue;
|
|
4406
|
+
out.push(candidate);
|
|
4407
|
+
seen.add(candidate.id);
|
|
4408
|
+
if (out.length >= limit) return out;
|
|
4409
|
+
}
|
|
4410
|
+
for (const candidate of hybridLane) {
|
|
4411
|
+
if (seen.has(candidate.id)) continue;
|
|
4412
|
+
out.push(candidate);
|
|
4413
|
+
seen.add(candidate.id);
|
|
4414
|
+
if (out.length >= limit) return out;
|
|
4415
|
+
}
|
|
4416
|
+
return out;
|
|
4417
|
+
}
|
|
4418
|
+
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
4419
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4420
|
+
for (const candidate of semanticCandidates) {
|
|
4421
|
+
byId.set(candidate.id, candidate);
|
|
4422
|
+
}
|
|
4423
|
+
for (const candidate of keywordCandidates) {
|
|
4424
|
+
const existing = byId.get(candidate.id);
|
|
4425
|
+
if (!existing || candidate.score > existing.score) {
|
|
4426
|
+
byId.set(candidate.id, candidate);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
return Array.from(byId.values());
|
|
4430
|
+
}
|
|
3437
4431
|
var Indexer = class {
|
|
3438
4432
|
config;
|
|
3439
4433
|
projectRoot;
|
|
@@ -3936,6 +4930,79 @@ var Indexer = class {
|
|
|
3936
4930
|
if (chunkDataBatch.length > 0) {
|
|
3937
4931
|
database.upsertChunksBatch(chunkDataBatch);
|
|
3938
4932
|
}
|
|
4933
|
+
const allSymbolIds = /* @__PURE__ */ new Set();
|
|
4934
|
+
const symbolsByFile = /* @__PURE__ */ new Map();
|
|
4935
|
+
for (let i = 0; i < parsedFiles.length; i++) {
|
|
4936
|
+
const parsed = parsedFiles[i];
|
|
4937
|
+
const changedFile = changedFiles[i];
|
|
4938
|
+
database.deleteCallEdgesByFile(parsed.path);
|
|
4939
|
+
database.deleteSymbolsByFile(parsed.path);
|
|
4940
|
+
const fileSymbols = [];
|
|
4941
|
+
for (const chunk of parsed.chunks) {
|
|
4942
|
+
if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
|
|
4943
|
+
const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
|
|
4944
|
+
const symbol = {
|
|
4945
|
+
id: symbolId,
|
|
4946
|
+
filePath: parsed.path,
|
|
4947
|
+
name: chunk.name,
|
|
4948
|
+
kind: chunk.chunkType,
|
|
4949
|
+
startLine: chunk.startLine,
|
|
4950
|
+
startCol: 0,
|
|
4951
|
+
endLine: chunk.endLine,
|
|
4952
|
+
endCol: 0,
|
|
4953
|
+
language: chunk.language
|
|
4954
|
+
};
|
|
4955
|
+
fileSymbols.push(symbol);
|
|
4956
|
+
allSymbolIds.add(symbolId);
|
|
4957
|
+
}
|
|
4958
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
4959
|
+
for (const symbol of fileSymbols) {
|
|
4960
|
+
const existing = symbolsByName.get(symbol.name) ?? [];
|
|
4961
|
+
existing.push(symbol);
|
|
4962
|
+
symbolsByName.set(symbol.name, existing);
|
|
4963
|
+
}
|
|
4964
|
+
if (fileSymbols.length > 0) {
|
|
4965
|
+
database.upsertSymbolsBatch(fileSymbols);
|
|
4966
|
+
symbolsByFile.set(parsed.path, fileSymbols);
|
|
4967
|
+
}
|
|
4968
|
+
const fileLanguage = parsed.chunks[0]?.language;
|
|
4969
|
+
if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
|
|
4970
|
+
const callSites = extractCalls(changedFile.content, fileLanguage);
|
|
4971
|
+
if (callSites.length === 0) continue;
|
|
4972
|
+
const edges = [];
|
|
4973
|
+
for (const site of callSites) {
|
|
4974
|
+
const enclosingSymbol = fileSymbols.find(
|
|
4975
|
+
(sym) => site.line >= sym.startLine && site.line <= sym.endLine
|
|
4976
|
+
);
|
|
4977
|
+
if (!enclosingSymbol) continue;
|
|
4978
|
+
const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
|
|
4979
|
+
edges.push({
|
|
4980
|
+
id: edgeId,
|
|
4981
|
+
fromSymbolId: enclosingSymbol.id,
|
|
4982
|
+
targetName: site.calleeName,
|
|
4983
|
+
toSymbolId: void 0,
|
|
4984
|
+
callType: site.callType,
|
|
4985
|
+
line: site.line,
|
|
4986
|
+
col: site.column,
|
|
4987
|
+
isResolved: false
|
|
4988
|
+
});
|
|
4989
|
+
}
|
|
4990
|
+
if (edges.length > 0) {
|
|
4991
|
+
database.upsertCallEdgesBatch(edges);
|
|
4992
|
+
for (const edge of edges) {
|
|
4993
|
+
const candidates = symbolsByName.get(edge.targetName);
|
|
4994
|
+
if (candidates && candidates.length === 1) {
|
|
4995
|
+
database.resolveCallEdge(edge.id, candidates[0].id);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
for (const filePath of unchangedFilePaths) {
|
|
5001
|
+
const existingSymbols = database.getSymbolsByFile(filePath);
|
|
5002
|
+
for (const sym of existingSymbols) {
|
|
5003
|
+
allSymbolIds.add(sym.id);
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
3939
5006
|
let removedCount = 0;
|
|
3940
5007
|
for (const [chunkId] of existingChunks) {
|
|
3941
5008
|
if (!currentChunkIds.has(chunkId)) {
|
|
@@ -3957,6 +5024,8 @@ var Indexer = class {
|
|
|
3957
5024
|
if (pendingChunks.length === 0 && removedCount === 0) {
|
|
3958
5025
|
database.clearBranch(this.currentBranch);
|
|
3959
5026
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5027
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5028
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
3960
5029
|
this.fileHashCache = currentFileHashes;
|
|
3961
5030
|
this.saveFileHashCache();
|
|
3962
5031
|
stats.durationMs = Date.now() - startTime;
|
|
@@ -3973,6 +5042,8 @@ var Indexer = class {
|
|
|
3973
5042
|
if (pendingChunks.length === 0) {
|
|
3974
5043
|
database.clearBranch(this.currentBranch);
|
|
3975
5044
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5045
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5046
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
3976
5047
|
store.save();
|
|
3977
5048
|
invertedIndex.save();
|
|
3978
5049
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4113,6 +5184,8 @@ var Indexer = class {
|
|
|
4113
5184
|
});
|
|
4114
5185
|
database.clearBranch(this.currentBranch);
|
|
4115
5186
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5187
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5188
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4116
5189
|
store.save();
|
|
4117
5190
|
invertedIndex.save();
|
|
4118
5191
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4223,15 +5296,22 @@ var Indexer = class {
|
|
|
4223
5296
|
}
|
|
4224
5297
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
4225
5298
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
5299
|
+
const fusionStrategy = this.config.search.fusionStrategy;
|
|
5300
|
+
const rrfK = this.config.search.rrfK;
|
|
5301
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
4226
5302
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
4227
5303
|
this.logger.search("debug", "Starting search", {
|
|
4228
5304
|
query,
|
|
4229
5305
|
maxResults,
|
|
4230
5306
|
hybridWeight,
|
|
5307
|
+
fusionStrategy,
|
|
5308
|
+
rrfK,
|
|
5309
|
+
rerankTopN,
|
|
4231
5310
|
filterByBranch
|
|
4232
5311
|
});
|
|
4233
5312
|
const embeddingStartTime = performance2.now();
|
|
4234
|
-
const
|
|
5313
|
+
const embeddingQuery = stripFilePathHint(query);
|
|
5314
|
+
const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
4235
5315
|
const embeddingMs = performance2.now() - embeddingStartTime;
|
|
4236
5316
|
const vectorStartTime = performance2.now();
|
|
4237
5317
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
@@ -4239,16 +5319,74 @@ var Indexer = class {
|
|
|
4239
5319
|
const keywordStartTime = performance2.now();
|
|
4240
5320
|
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
4241
5321
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
4242
|
-
const fusionStartTime = performance2.now();
|
|
4243
|
-
const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
|
|
4244
|
-
const fusionMs = performance2.now() - fusionStartTime;
|
|
4245
5322
|
let branchChunkIds = null;
|
|
4246
5323
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4247
5324
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4248
5325
|
}
|
|
4249
|
-
const
|
|
5326
|
+
const prefilterStartTime = performance2.now();
|
|
5327
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5328
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5329
|
+
const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
|
|
5330
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5331
|
+
const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
|
|
5332
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5333
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5334
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5335
|
+
branch: this.currentBranch
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5339
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5340
|
+
branch: this.currentBranch
|
|
5341
|
+
});
|
|
5342
|
+
}
|
|
5343
|
+
if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
|
|
5344
|
+
this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
|
|
5345
|
+
branch: this.currentBranch
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5348
|
+
const fusionStartTime = performance2.now();
|
|
5349
|
+
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
5350
|
+
fusionStrategy,
|
|
5351
|
+
rrfK,
|
|
5352
|
+
rerankTopN,
|
|
5353
|
+
limit: maxResults,
|
|
5354
|
+
hybridWeight
|
|
5355
|
+
});
|
|
5356
|
+
const fusionMs = performance2.now() - fusionStartTime;
|
|
5357
|
+
const rescued = promoteIdentifierMatches(
|
|
5358
|
+
query,
|
|
5359
|
+
combined,
|
|
5360
|
+
semanticCandidates,
|
|
5361
|
+
keywordCandidates,
|
|
5362
|
+
database,
|
|
5363
|
+
branchChunkIds
|
|
5364
|
+
);
|
|
5365
|
+
const union = unionCandidates(semanticCandidates, keywordCandidates);
|
|
5366
|
+
const deterministicIdentifierLane = buildDeterministicIdentifierPass(
|
|
5367
|
+
query,
|
|
5368
|
+
union,
|
|
5369
|
+
maxResults
|
|
5370
|
+
);
|
|
5371
|
+
const identifierLane = buildIdentifierDefinitionLane(
|
|
5372
|
+
query,
|
|
5373
|
+
union,
|
|
5374
|
+
maxResults
|
|
5375
|
+
);
|
|
5376
|
+
const symbolLane = buildSymbolDefinitionLane(
|
|
5377
|
+
query,
|
|
5378
|
+
database,
|
|
5379
|
+
branchChunkIds,
|
|
5380
|
+
maxResults,
|
|
5381
|
+
union
|
|
5382
|
+
);
|
|
5383
|
+
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5384
|
+
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5385
|
+
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5386
|
+
const sourceIntent = classifyQueryIntentRaw(query) === "source";
|
|
5387
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
|
|
5388
|
+
const baseFiltered = tiered.filter((r) => {
|
|
4250
5389
|
if (r.score < this.config.search.minScore) return false;
|
|
4251
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4252
5390
|
if (options?.fileType) {
|
|
4253
5391
|
const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
|
|
4254
5392
|
if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
|
|
@@ -4261,7 +5399,11 @@ var Indexer = class {
|
|
|
4261
5399
|
if (r.metadata.chunkType !== options.chunkType) return false;
|
|
4262
5400
|
}
|
|
4263
5401
|
return true;
|
|
4264
|
-
})
|
|
5402
|
+
});
|
|
5403
|
+
const implementationOnly = baseFiltered.filter(
|
|
5404
|
+
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5405
|
+
);
|
|
5406
|
+
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
4265
5407
|
const totalSearchMs = performance2.now() - searchStartTime;
|
|
4266
5408
|
this.logger.recordSearch(totalSearchMs, {
|
|
4267
5409
|
embeddingMs,
|
|
@@ -4276,6 +5418,7 @@ var Indexer = class {
|
|
|
4276
5418
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4277
5419
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
4278
5420
|
keywordMs: Math.round(keywordMs * 100) / 100,
|
|
5421
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100,
|
|
4279
5422
|
fusionMs: Math.round(fusionMs * 100) / 100
|
|
4280
5423
|
});
|
|
4281
5424
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
@@ -4329,34 +5472,6 @@ var Indexer = class {
|
|
|
4329
5472
|
results.sort((a, b) => b.score - a.score);
|
|
4330
5473
|
return results.slice(0, limit);
|
|
4331
5474
|
}
|
|
4332
|
-
fuseResults(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4333
|
-
const semanticWeight = 1 - keywordWeight;
|
|
4334
|
-
const fusedScores = /* @__PURE__ */ new Map();
|
|
4335
|
-
for (const r of semanticResults) {
|
|
4336
|
-
fusedScores.set(r.id, {
|
|
4337
|
-
score: r.score * semanticWeight,
|
|
4338
|
-
metadata: r.metadata
|
|
4339
|
-
});
|
|
4340
|
-
}
|
|
4341
|
-
for (const r of keywordResults) {
|
|
4342
|
-
const existing = fusedScores.get(r.id);
|
|
4343
|
-
if (existing) {
|
|
4344
|
-
existing.score += r.score * keywordWeight;
|
|
4345
|
-
} else {
|
|
4346
|
-
fusedScores.set(r.id, {
|
|
4347
|
-
score: r.score * keywordWeight,
|
|
4348
|
-
metadata: r.metadata
|
|
4349
|
-
});
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4353
|
-
id,
|
|
4354
|
-
score: data.score,
|
|
4355
|
-
metadata: data.metadata
|
|
4356
|
-
}));
|
|
4357
|
-
results.sort((a, b) => b.score - a.score);
|
|
4358
|
-
return results.slice(0, limit);
|
|
4359
|
-
}
|
|
4360
5475
|
async getStatus() {
|
|
4361
5476
|
const { store, configuredProviderInfo } = await this.ensureInitialized();
|
|
4362
5477
|
return {
|
|
@@ -4379,6 +5494,7 @@ var Indexer = class {
|
|
|
4379
5494
|
this.fileHashCache.clear();
|
|
4380
5495
|
this.saveFileHashCache();
|
|
4381
5496
|
database.clearBranch(this.currentBranch);
|
|
5497
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
4382
5498
|
database.deleteMetadata("index.version");
|
|
4383
5499
|
database.deleteMetadata("index.embeddingProvider");
|
|
4384
5500
|
database.deleteMetadata("index.embeddingModel");
|
|
@@ -4407,6 +5523,8 @@ var Indexer = class {
|
|
|
4407
5523
|
removedCount++;
|
|
4408
5524
|
}
|
|
4409
5525
|
database.deleteChunksByFile(filePath);
|
|
5526
|
+
database.deleteCallEdgesByFile(filePath);
|
|
5527
|
+
database.deleteSymbolsByFile(filePath);
|
|
4410
5528
|
removedFilePaths.push(filePath);
|
|
4411
5529
|
}
|
|
4412
5530
|
}
|
|
@@ -4416,6 +5534,8 @@ var Indexer = class {
|
|
|
4416
5534
|
}
|
|
4417
5535
|
const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
|
|
4418
5536
|
const gcOrphanChunks = database.gcOrphanChunks();
|
|
5537
|
+
const gcOrphanSymbols = database.gcOrphanSymbols();
|
|
5538
|
+
const gcOrphanCallEdges = database.gcOrphanCallEdges();
|
|
4419
5539
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
4420
5540
|
this.logger.gc("info", "Health check complete", {
|
|
4421
5541
|
removedStale: removedCount,
|
|
@@ -4423,7 +5543,7 @@ var Indexer = class {
|
|
|
4423
5543
|
orphanChunks: gcOrphanChunks,
|
|
4424
5544
|
removedFiles: removedFilePaths.length
|
|
4425
5545
|
});
|
|
4426
|
-
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
|
|
5546
|
+
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
4427
5547
|
}
|
|
4428
5548
|
async retryFailedBatches() {
|
|
4429
5549
|
const { store, provider, invertedIndex } = await this.ensureInitialized();
|
|
@@ -4529,9 +5649,29 @@ var Indexer = class {
|
|
|
4529
5649
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4530
5650
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4531
5651
|
}
|
|
4532
|
-
const
|
|
5652
|
+
const prefilterStartTime = performance2.now();
|
|
5653
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5654
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5655
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5656
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5657
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5658
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5659
|
+
branch: this.currentBranch
|
|
5660
|
+
});
|
|
5661
|
+
}
|
|
5662
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5663
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5664
|
+
branch: this.currentBranch
|
|
5665
|
+
});
|
|
5666
|
+
}
|
|
5667
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
5668
|
+
const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
|
|
5669
|
+
rerankTopN,
|
|
5670
|
+
limit,
|
|
5671
|
+
prioritizeSourcePaths: false
|
|
5672
|
+
});
|
|
5673
|
+
const filtered = ranked.filter((r) => {
|
|
4533
5674
|
if (r.score < this.config.search.minScore) return false;
|
|
4534
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4535
5675
|
if (options?.excludeFile) {
|
|
4536
5676
|
if (r.metadata.filePath === options.excludeFile) return false;
|
|
4537
5677
|
}
|
|
@@ -4560,7 +5700,8 @@ var Indexer = class {
|
|
|
4560
5700
|
results: filtered.length,
|
|
4561
5701
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
4562
5702
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4563
|
-
vectorMs: Math.round(vectorMs * 100) / 100
|
|
5703
|
+
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
5704
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100
|
|
4564
5705
|
});
|
|
4565
5706
|
return Promise.all(
|
|
4566
5707
|
filtered.map(async (r) => {
|
|
@@ -4589,6 +5730,14 @@ var Indexer = class {
|
|
|
4589
5730
|
})
|
|
4590
5731
|
);
|
|
4591
5732
|
}
|
|
5733
|
+
async getCallers(targetName) {
|
|
5734
|
+
const { database } = await this.ensureInitialized();
|
|
5735
|
+
return database.getCallersWithContext(targetName, this.currentBranch);
|
|
5736
|
+
}
|
|
5737
|
+
async getCallees(symbolId) {
|
|
5738
|
+
const { database } = await this.ensureInitialized();
|
|
5739
|
+
return database.getCallees(symbolId, this.currentBranch);
|
|
5740
|
+
}
|
|
4592
5741
|
};
|
|
4593
5742
|
|
|
4594
5743
|
// src/mcp-server.ts
|
|
@@ -4791,7 +5940,7 @@ Use Read tool to examine specific files.` }] };
|
|
|
4791
5940
|
async () => {
|
|
4792
5941
|
await ensureInitialized();
|
|
4793
5942
|
const result = await indexer.healthCheck();
|
|
4794
|
-
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
|
|
5943
|
+
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
|
|
4795
5944
|
return { content: [{ type: "text", text: "Index is healthy. No stale entries found." }] };
|
|
4796
5945
|
}
|
|
4797
5946
|
const lines = [`Health check complete:`];
|
|
@@ -4804,6 +5953,12 @@ Use Read tool to examine specific files.` }] };
|
|
|
4804
5953
|
if (result.gcOrphanChunks > 0) {
|
|
4805
5954
|
lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
|
|
4806
5955
|
}
|
|
5956
|
+
if (result.gcOrphanSymbols > 0) {
|
|
5957
|
+
lines.push(` Garbage collected orphan symbols: ${result.gcOrphanSymbols}`);
|
|
5958
|
+
}
|
|
5959
|
+
if (result.gcOrphanCallEdges > 0) {
|
|
5960
|
+
lines.push(` Garbage collected orphan call edges: ${result.gcOrphanCallEdges}`);
|
|
5961
|
+
}
|
|
4807
5962
|
if (result.filePaths.length > 0) {
|
|
4808
5963
|
lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
|
|
4809
5964
|
}
|
|
@@ -4892,6 +6047,43 @@ ${truncateContent(r.content)}
|
|
|
4892
6047
|
${formatted.join("\n\n")}` }] };
|
|
4893
6048
|
}
|
|
4894
6049
|
);
|
|
6050
|
+
server.tool(
|
|
6051
|
+
"call_graph",
|
|
6052
|
+
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
|
|
6053
|
+
{
|
|
6054
|
+
name: z.string().describe("Function or method name to query"),
|
|
6055
|
+
direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
6056
|
+
symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction)")
|
|
6057
|
+
},
|
|
6058
|
+
async (args) => {
|
|
6059
|
+
await ensureInitialized();
|
|
6060
|
+
if (args.direction === "callees") {
|
|
6061
|
+
if (!args.symbolId) {
|
|
6062
|
+
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
6063
|
+
}
|
|
6064
|
+
const callees = await indexer.getCallees(args.symbolId);
|
|
6065
|
+
if (callees.length === 0) {
|
|
6066
|
+
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}.` }] };
|
|
6067
|
+
}
|
|
6068
|
+
const formatted2 = callees.map(
|
|
6069
|
+
(e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
|
|
6070
|
+
);
|
|
6071
|
+
return { content: [{ type: "text", text: `Callees (${callees.length}):
|
|
6072
|
+
|
|
6073
|
+
${formatted2.join("\n")}` }] };
|
|
6074
|
+
}
|
|
6075
|
+
const callers = await indexer.getCallers(args.name);
|
|
6076
|
+
if (callers.length === 0) {
|
|
6077
|
+
return { content: [{ type: "text", text: `No callers found for "${args.name}".` }] };
|
|
6078
|
+
}
|
|
6079
|
+
const formatted = callers.map(
|
|
6080
|
+
(e, i) => `[${i + 1}] \u2190 from ${e.fromSymbolName ?? "<unknown>"} in ${e.fromSymbolFilePath ?? "<unknown file>"} [${e.fromSymbolId}] (${e.callType}) at line ${e.line}${e.isResolved ? " [resolved]" : " [unresolved]"}`
|
|
6081
|
+
);
|
|
6082
|
+
return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
|
|
6083
|
+
|
|
6084
|
+
${formatted.join("\n")}` }] };
|
|
6085
|
+
}
|
|
6086
|
+
);
|
|
4895
6087
|
server.prompt(
|
|
4896
6088
|
"search",
|
|
4897
6089
|
"Search codebase by meaning using semantic search",
|