opencode-codebase-index 0.5.1 → 0.6.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 +226 -107
- package/commands/call-graph.md +24 -0
- package/commands/definition.md +24 -0
- package/dist/cli.cjs +2624 -269
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2626 -277
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1324 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1325 -81
- 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 +9 -3
- package/skill/SKILL.md +13 -1
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
33
33
|
|
|
34
34
|
// node_modules/eventemitter3/index.js
|
|
35
35
|
var require_eventemitter3 = __commonJS({
|
|
36
|
-
"node_modules/eventemitter3/index.js"(exports2,
|
|
36
|
+
"node_modules/eventemitter3/index.js"(exports2, module3) {
|
|
37
37
|
"use strict";
|
|
38
38
|
var has = Object.prototype.hasOwnProperty;
|
|
39
39
|
var prefix = "~";
|
|
@@ -187,15 +187,15 @@ var require_eventemitter3 = __commonJS({
|
|
|
187
187
|
EventEmitter3.prototype.addListener = EventEmitter3.prototype.on;
|
|
188
188
|
EventEmitter3.prefixed = prefix;
|
|
189
189
|
EventEmitter3.EventEmitter = EventEmitter3;
|
|
190
|
-
if ("undefined" !== typeof
|
|
191
|
-
|
|
190
|
+
if ("undefined" !== typeof module3) {
|
|
191
|
+
module3.exports = EventEmitter3;
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
});
|
|
195
195
|
|
|
196
196
|
// node_modules/ignore/index.js
|
|
197
197
|
var require_ignore = __commonJS({
|
|
198
|
-
"node_modules/ignore/index.js"(exports2,
|
|
198
|
+
"node_modules/ignore/index.js"(exports2, module3) {
|
|
199
199
|
"use strict";
|
|
200
200
|
function makeArray(subject) {
|
|
201
201
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -644,10 +644,10 @@ var require_ignore = __commonJS({
|
|
|
644
644
|
) {
|
|
645
645
|
setupWindows();
|
|
646
646
|
}
|
|
647
|
-
|
|
647
|
+
module3.exports = factory;
|
|
648
648
|
factory.default = factory;
|
|
649
|
-
|
|
650
|
-
define(
|
|
649
|
+
module3.exports.isPathValid = isPathValid;
|
|
650
|
+
define(module3.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
651
651
|
}
|
|
652
652
|
});
|
|
653
653
|
|
|
@@ -668,7 +668,7 @@ var DEFAULT_INCLUDE = [
|
|
|
668
668
|
"**/*.{py,pyi}",
|
|
669
669
|
"**/*.{go,rs,java,kt,scala}",
|
|
670
670
|
"**/*.{c,cpp,cc,h,hpp}",
|
|
671
|
-
"**/*.{rb,php,swift}",
|
|
671
|
+
"**/*.{rb,php,inc,swift}",
|
|
672
672
|
"**/*.{vue,svelte,astro}",
|
|
673
673
|
"**/*.{sql,graphql,proto}",
|
|
674
674
|
"**/*.{yaml,yml,toml}",
|
|
@@ -785,9 +785,15 @@ function getDefaultSearchConfig() {
|
|
|
785
785
|
minScore: 0.1,
|
|
786
786
|
includeContext: true,
|
|
787
787
|
hybridWeight: 0.5,
|
|
788
|
+
fusionStrategy: "rrf",
|
|
789
|
+
rrfK: 60,
|
|
790
|
+
rerankTopN: 20,
|
|
788
791
|
contextLines: 0
|
|
789
792
|
};
|
|
790
793
|
}
|
|
794
|
+
function isValidFusionStrategy(value) {
|
|
795
|
+
return value === "weighted" || value === "rrf";
|
|
796
|
+
}
|
|
791
797
|
function getDefaultDebugConfig() {
|
|
792
798
|
return {
|
|
793
799
|
enabled: false,
|
|
@@ -842,6 +848,9 @@ function parseConfig(raw) {
|
|
|
842
848
|
minScore: typeof rawSearch.minScore === "number" ? rawSearch.minScore : defaultSearch.minScore,
|
|
843
849
|
includeContext: typeof rawSearch.includeContext === "boolean" ? rawSearch.includeContext : defaultSearch.includeContext,
|
|
844
850
|
hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
|
|
851
|
+
fusionStrategy: isValidFusionStrategy(rawSearch.fusionStrategy) ? rawSearch.fusionStrategy : defaultSearch.fusionStrategy,
|
|
852
|
+
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
853
|
+
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
845
854
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
|
|
846
855
|
};
|
|
847
856
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -2920,6 +2929,7 @@ function initializeLogger(config) {
|
|
|
2920
2929
|
// src/native/index.ts
|
|
2921
2930
|
var path3 = __toESM(require("path"), 1);
|
|
2922
2931
|
var os2 = __toESM(require("os"), 1);
|
|
2932
|
+
var module2 = __toESM(require("module"), 1);
|
|
2923
2933
|
var import_url = require("url");
|
|
2924
2934
|
var import_meta = {};
|
|
2925
2935
|
function getNativeBinding() {
|
|
@@ -2940,17 +2950,22 @@ function getNativeBinding() {
|
|
|
2940
2950
|
throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
|
|
2941
2951
|
}
|
|
2942
2952
|
let currentDir;
|
|
2953
|
+
let requireTarget;
|
|
2943
2954
|
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
2944
2955
|
currentDir = path3.dirname((0, import_url.fileURLToPath)(import_meta.url));
|
|
2956
|
+
requireTarget = import_meta.url;
|
|
2945
2957
|
} else if (typeof __dirname !== "undefined") {
|
|
2946
2958
|
currentDir = __dirname;
|
|
2959
|
+
requireTarget = __filename;
|
|
2947
2960
|
} else {
|
|
2948
2961
|
currentDir = process.cwd();
|
|
2962
|
+
requireTarget = path3.join(currentDir, "index.js");
|
|
2949
2963
|
}
|
|
2950
2964
|
const isDevMode = currentDir.includes("/src/native");
|
|
2951
2965
|
const packageRoot = isDevMode ? path3.resolve(currentDir, "../..") : path3.resolve(currentDir, "..");
|
|
2952
2966
|
const nativePath = path3.join(packageRoot, "native", bindingName);
|
|
2953
|
-
|
|
2967
|
+
const require2 = module2.createRequire(requireTarget);
|
|
2968
|
+
return require2(nativePath);
|
|
2954
2969
|
}
|
|
2955
2970
|
var native = getNativeBinding();
|
|
2956
2971
|
function parseFiles(files) {
|
|
@@ -2977,6 +2992,9 @@ function hashContent(content) {
|
|
|
2977
2992
|
function hashFile(filePath) {
|
|
2978
2993
|
return native.hashFile(filePath);
|
|
2979
2994
|
}
|
|
2995
|
+
function extractCalls(content, language) {
|
|
2996
|
+
return native.extractCalls(content, language);
|
|
2997
|
+
}
|
|
2980
2998
|
var VectorStore = class {
|
|
2981
2999
|
inner;
|
|
2982
3000
|
dimensions;
|
|
@@ -3308,6 +3326,12 @@ var Database = class {
|
|
|
3308
3326
|
getChunksByFile(filePath) {
|
|
3309
3327
|
return this.inner.getChunksByFile(filePath);
|
|
3310
3328
|
}
|
|
3329
|
+
getChunksByName(name) {
|
|
3330
|
+
return this.inner.getChunksByName(name);
|
|
3331
|
+
}
|
|
3332
|
+
getChunksByNameCi(name) {
|
|
3333
|
+
return this.inner.getChunksByNameCi(name);
|
|
3334
|
+
}
|
|
3311
3335
|
deleteChunksByFile(filePath) {
|
|
3312
3336
|
return this.inner.deleteChunksByFile(filePath);
|
|
3313
3337
|
}
|
|
@@ -3351,12 +3375,108 @@ var Database = class {
|
|
|
3351
3375
|
getStats() {
|
|
3352
3376
|
return this.inner.getStats();
|
|
3353
3377
|
}
|
|
3378
|
+
// ── Symbol methods ──────────────────────────────────────────────
|
|
3379
|
+
upsertSymbol(symbol) {
|
|
3380
|
+
this.inner.upsertSymbol(symbol);
|
|
3381
|
+
}
|
|
3382
|
+
upsertSymbolsBatch(symbols) {
|
|
3383
|
+
if (symbols.length === 0) return;
|
|
3384
|
+
this.inner.upsertSymbolsBatch(symbols);
|
|
3385
|
+
}
|
|
3386
|
+
getSymbolsByFile(filePath) {
|
|
3387
|
+
return this.inner.getSymbolsByFile(filePath);
|
|
3388
|
+
}
|
|
3389
|
+
getSymbolByName(name, filePath) {
|
|
3390
|
+
return this.inner.getSymbolByName(name, filePath) ?? null;
|
|
3391
|
+
}
|
|
3392
|
+
getSymbolsByName(name) {
|
|
3393
|
+
return this.inner.getSymbolsByName(name);
|
|
3394
|
+
}
|
|
3395
|
+
getSymbolsByNameCi(name) {
|
|
3396
|
+
return this.inner.getSymbolsByNameCi(name);
|
|
3397
|
+
}
|
|
3398
|
+
deleteSymbolsByFile(filePath) {
|
|
3399
|
+
return this.inner.deleteSymbolsByFile(filePath);
|
|
3400
|
+
}
|
|
3401
|
+
// ── Call Edge methods ────────────────────────────────────────────
|
|
3402
|
+
upsertCallEdge(edge) {
|
|
3403
|
+
this.inner.upsertCallEdge(edge);
|
|
3404
|
+
}
|
|
3405
|
+
upsertCallEdgesBatch(edges) {
|
|
3406
|
+
if (edges.length === 0) return;
|
|
3407
|
+
this.inner.upsertCallEdgesBatch(edges);
|
|
3408
|
+
}
|
|
3409
|
+
getCallers(targetName, branch) {
|
|
3410
|
+
return this.inner.getCallers(targetName, branch);
|
|
3411
|
+
}
|
|
3412
|
+
getCallersWithContext(targetName, branch) {
|
|
3413
|
+
return this.inner.getCallersWithContext(targetName, branch);
|
|
3414
|
+
}
|
|
3415
|
+
getCallees(symbolId, branch) {
|
|
3416
|
+
return this.inner.getCallees(symbolId, branch);
|
|
3417
|
+
}
|
|
3418
|
+
deleteCallEdgesByFile(filePath) {
|
|
3419
|
+
return this.inner.deleteCallEdgesByFile(filePath);
|
|
3420
|
+
}
|
|
3421
|
+
resolveCallEdge(edgeId, toSymbolId) {
|
|
3422
|
+
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
3423
|
+
}
|
|
3424
|
+
// ── Branch Symbol methods ────────────────────────────────────────
|
|
3425
|
+
addSymbolsToBranch(branch, symbolIds) {
|
|
3426
|
+
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
3427
|
+
}
|
|
3428
|
+
addSymbolsToBranchBatch(branch, symbolIds) {
|
|
3429
|
+
if (symbolIds.length === 0) return;
|
|
3430
|
+
this.inner.addSymbolsToBranchBatch(branch, symbolIds);
|
|
3431
|
+
}
|
|
3432
|
+
getBranchSymbolIds(branch) {
|
|
3433
|
+
return this.inner.getBranchSymbolIds(branch);
|
|
3434
|
+
}
|
|
3435
|
+
clearBranchSymbols(branch) {
|
|
3436
|
+
return this.inner.clearBranchSymbols(branch);
|
|
3437
|
+
}
|
|
3438
|
+
// ── GC methods for symbols/edges ─────────────────────────────────
|
|
3439
|
+
gcOrphanSymbols() {
|
|
3440
|
+
return this.inner.gcOrphanSymbols();
|
|
3441
|
+
}
|
|
3442
|
+
gcOrphanCallEdges() {
|
|
3443
|
+
return this.inner.gcOrphanCallEdges();
|
|
3444
|
+
}
|
|
3354
3445
|
};
|
|
3355
3446
|
|
|
3356
3447
|
// src/git/index.ts
|
|
3357
3448
|
var import_fs3 = require("fs");
|
|
3358
3449
|
var path4 = __toESM(require("path"), 1);
|
|
3359
|
-
|
|
3450
|
+
function readPackedRefs(gitDir) {
|
|
3451
|
+
const packedRefsPath = path4.join(gitDir, "packed-refs");
|
|
3452
|
+
if (!(0, import_fs3.existsSync)(packedRefsPath)) {
|
|
3453
|
+
return [];
|
|
3454
|
+
}
|
|
3455
|
+
try {
|
|
3456
|
+
return (0, import_fs3.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
3457
|
+
} catch {
|
|
3458
|
+
return [];
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
function resolveCommonGitDir(gitDir) {
|
|
3462
|
+
const commonDirPath = path4.join(gitDir, "commondir");
|
|
3463
|
+
if (!(0, import_fs3.existsSync)(commonDirPath)) {
|
|
3464
|
+
return gitDir;
|
|
3465
|
+
}
|
|
3466
|
+
try {
|
|
3467
|
+
const raw = (0, import_fs3.readFileSync)(commonDirPath, "utf-8").trim();
|
|
3468
|
+
if (!raw) {
|
|
3469
|
+
return gitDir;
|
|
3470
|
+
}
|
|
3471
|
+
const resolved = path4.isAbsolute(raw) ? raw : path4.resolve(gitDir, raw);
|
|
3472
|
+
if ((0, import_fs3.existsSync)(resolved)) {
|
|
3473
|
+
return resolved;
|
|
3474
|
+
}
|
|
3475
|
+
} catch {
|
|
3476
|
+
return gitDir;
|
|
3477
|
+
}
|
|
3478
|
+
return gitDir;
|
|
3479
|
+
}
|
|
3360
3480
|
function resolveGitDir(repoRoot) {
|
|
3361
3481
|
const gitPath = path4.join(repoRoot, ".git");
|
|
3362
3482
|
if (!(0, import_fs3.existsSync)(gitPath)) {
|
|
@@ -3410,37 +3530,20 @@ function getCurrentBranch(repoRoot) {
|
|
|
3410
3530
|
}
|
|
3411
3531
|
function getBaseBranch(repoRoot) {
|
|
3412
3532
|
const gitDir = resolveGitDir(repoRoot);
|
|
3533
|
+
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
3413
3534
|
const candidates = ["main", "master", "develop", "trunk"];
|
|
3414
|
-
if (
|
|
3535
|
+
if (refStoreDir) {
|
|
3415
3536
|
for (const candidate of candidates) {
|
|
3416
|
-
const refPath = path4.join(
|
|
3537
|
+
const refPath = path4.join(refStoreDir, "refs", "heads", candidate);
|
|
3417
3538
|
if ((0, import_fs3.existsSync)(refPath)) {
|
|
3418
3539
|
return candidate;
|
|
3419
3540
|
}
|
|
3420
|
-
const
|
|
3421
|
-
if ((
|
|
3422
|
-
|
|
3423
|
-
const content = (0, import_fs3.readFileSync)(packedRefsPath, "utf-8");
|
|
3424
|
-
if (content.includes(`refs/heads/${candidate}`)) {
|
|
3425
|
-
return candidate;
|
|
3426
|
-
}
|
|
3427
|
-
} catch {
|
|
3428
|
-
}
|
|
3541
|
+
const packedRefs = readPackedRefs(refStoreDir);
|
|
3542
|
+
if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
|
|
3543
|
+
return candidate;
|
|
3429
3544
|
}
|
|
3430
3545
|
}
|
|
3431
3546
|
}
|
|
3432
|
-
try {
|
|
3433
|
-
const result = (0, import_child_process.execSync)("git remote show origin", {
|
|
3434
|
-
cwd: repoRoot,
|
|
3435
|
-
encoding: "utf-8",
|
|
3436
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3437
|
-
});
|
|
3438
|
-
const match = result.match(/HEAD branch: (.+)/);
|
|
3439
|
-
if (match) {
|
|
3440
|
-
return match[1].trim();
|
|
3441
|
-
}
|
|
3442
|
-
} catch {
|
|
3443
|
-
}
|
|
3444
3547
|
return getCurrentBranch(repoRoot) ?? "main";
|
|
3445
3548
|
}
|
|
3446
3549
|
function getBranchOrDefault(repoRoot) {
|
|
@@ -3458,6 +3561,30 @@ function getHeadPath(repoRoot) {
|
|
|
3458
3561
|
}
|
|
3459
3562
|
|
|
3460
3563
|
// src/indexer/index.ts
|
|
3564
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
|
|
3565
|
+
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
3566
|
+
"function_declaration",
|
|
3567
|
+
"function",
|
|
3568
|
+
"arrow_function",
|
|
3569
|
+
"method_definition",
|
|
3570
|
+
"class_declaration",
|
|
3571
|
+
"interface_declaration",
|
|
3572
|
+
"type_alias_declaration",
|
|
3573
|
+
"enum_declaration",
|
|
3574
|
+
"function_definition",
|
|
3575
|
+
"class_definition",
|
|
3576
|
+
"decorated_definition",
|
|
3577
|
+
"method_declaration",
|
|
3578
|
+
"type_declaration",
|
|
3579
|
+
"type_spec",
|
|
3580
|
+
"function_item",
|
|
3581
|
+
"impl_item",
|
|
3582
|
+
"struct_item",
|
|
3583
|
+
"enum_item",
|
|
3584
|
+
"trait_item",
|
|
3585
|
+
"mod_item",
|
|
3586
|
+
"trait_declaration"
|
|
3587
|
+
]);
|
|
3461
3588
|
function float32ArrayToBuffer(arr) {
|
|
3462
3589
|
const float32 = new Float32Array(arr);
|
|
3463
3590
|
return Buffer.from(float32.buffer);
|
|
@@ -3482,6 +3609,891 @@ function isRateLimitError(error) {
|
|
|
3482
3609
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
3483
3610
|
}
|
|
3484
3611
|
var INDEX_METADATA_VERSION = "1";
|
|
3612
|
+
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
3613
|
+
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
3614
|
+
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
3615
|
+
var rankingPathTokenCache = /* @__PURE__ */ new Map();
|
|
3616
|
+
var rankingTextTokenCache = /* @__PURE__ */ new Map();
|
|
3617
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3618
|
+
"the",
|
|
3619
|
+
"and",
|
|
3620
|
+
"for",
|
|
3621
|
+
"with",
|
|
3622
|
+
"from",
|
|
3623
|
+
"that",
|
|
3624
|
+
"this",
|
|
3625
|
+
"into",
|
|
3626
|
+
"using",
|
|
3627
|
+
"where",
|
|
3628
|
+
"what",
|
|
3629
|
+
"when",
|
|
3630
|
+
"why",
|
|
3631
|
+
"how",
|
|
3632
|
+
"are",
|
|
3633
|
+
"was",
|
|
3634
|
+
"were",
|
|
3635
|
+
"be",
|
|
3636
|
+
"been",
|
|
3637
|
+
"being",
|
|
3638
|
+
"find",
|
|
3639
|
+
"show",
|
|
3640
|
+
"get",
|
|
3641
|
+
"run",
|
|
3642
|
+
"use",
|
|
3643
|
+
"code",
|
|
3644
|
+
"function",
|
|
3645
|
+
"implementation",
|
|
3646
|
+
"retrieve",
|
|
3647
|
+
"results",
|
|
3648
|
+
"result",
|
|
3649
|
+
"search",
|
|
3650
|
+
"pipeline",
|
|
3651
|
+
"top",
|
|
3652
|
+
"in",
|
|
3653
|
+
"on",
|
|
3654
|
+
"of",
|
|
3655
|
+
"to",
|
|
3656
|
+
"by",
|
|
3657
|
+
"as",
|
|
3658
|
+
"or",
|
|
3659
|
+
"an",
|
|
3660
|
+
"a"
|
|
3661
|
+
]);
|
|
3662
|
+
var TEST_PATH_SEGMENTS = [
|
|
3663
|
+
"tests/",
|
|
3664
|
+
"__tests__/",
|
|
3665
|
+
"/test/",
|
|
3666
|
+
"fixtures/",
|
|
3667
|
+
"benchmark",
|
|
3668
|
+
"README",
|
|
3669
|
+
"ARCHITECTURE",
|
|
3670
|
+
"docs/"
|
|
3671
|
+
];
|
|
3672
|
+
var IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS = [
|
|
3673
|
+
"tests/",
|
|
3674
|
+
"__tests__/",
|
|
3675
|
+
"/test/",
|
|
3676
|
+
"fixtures/",
|
|
3677
|
+
"benchmark",
|
|
3678
|
+
"readme",
|
|
3679
|
+
"architecture",
|
|
3680
|
+
"docs/",
|
|
3681
|
+
"examples/",
|
|
3682
|
+
"example/",
|
|
3683
|
+
".github/",
|
|
3684
|
+
"/scripts/",
|
|
3685
|
+
"/migrations/",
|
|
3686
|
+
"/generated/"
|
|
3687
|
+
];
|
|
3688
|
+
var SOURCE_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3689
|
+
"implement",
|
|
3690
|
+
"implementation",
|
|
3691
|
+
"function",
|
|
3692
|
+
"method",
|
|
3693
|
+
"class",
|
|
3694
|
+
"logic",
|
|
3695
|
+
"algorithm",
|
|
3696
|
+
"pipeline",
|
|
3697
|
+
"indexer",
|
|
3698
|
+
"where"
|
|
3699
|
+
]);
|
|
3700
|
+
var DOC_TEST_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3701
|
+
"test",
|
|
3702
|
+
"tests",
|
|
3703
|
+
"fixture",
|
|
3704
|
+
"fixtures",
|
|
3705
|
+
"benchmark",
|
|
3706
|
+
"readme",
|
|
3707
|
+
"docs",
|
|
3708
|
+
"documentation"
|
|
3709
|
+
]);
|
|
3710
|
+
var DOC_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3711
|
+
"readme",
|
|
3712
|
+
"docs",
|
|
3713
|
+
"documentation",
|
|
3714
|
+
"guide",
|
|
3715
|
+
"usage"
|
|
3716
|
+
]);
|
|
3717
|
+
function setBoundedCache(cache, key, value) {
|
|
3718
|
+
if (cache.size >= RANKING_TOKEN_CACHE_LIMIT) {
|
|
3719
|
+
const oldest = cache.keys().next().value;
|
|
3720
|
+
if (oldest !== void 0) {
|
|
3721
|
+
cache.delete(oldest);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
cache.set(key, value);
|
|
3725
|
+
}
|
|
3726
|
+
function tokenizeTextForRanking(text) {
|
|
3727
|
+
if (!text) {
|
|
3728
|
+
return /* @__PURE__ */ new Set();
|
|
3729
|
+
}
|
|
3730
|
+
const lowered = text.toLowerCase();
|
|
3731
|
+
const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
|
|
3732
|
+
if (cache) {
|
|
3733
|
+
return cache;
|
|
3734
|
+
}
|
|
3735
|
+
const tokens = new Set(
|
|
3736
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !STOPWORDS.has(token))
|
|
3737
|
+
);
|
|
3738
|
+
setBoundedCache(rankingQueryTokenCache, lowered, tokens);
|
|
3739
|
+
setBoundedCache(rankingTextTokenCache, lowered, tokens);
|
|
3740
|
+
return tokens;
|
|
3741
|
+
}
|
|
3742
|
+
function splitPathTokens(filePath) {
|
|
3743
|
+
const lowered = filePath.toLowerCase();
|
|
3744
|
+
const cache = rankingPathTokenCache.get(lowered);
|
|
3745
|
+
if (cache) {
|
|
3746
|
+
return cache;
|
|
3747
|
+
}
|
|
3748
|
+
const normalized = lowered.replace(/[^a-z0-9/._-]/g, " ").split(/[/._-]+/).filter((token) => token.length > 1);
|
|
3749
|
+
const tokens = new Set(normalized);
|
|
3750
|
+
setBoundedCache(rankingPathTokenCache, lowered, tokens);
|
|
3751
|
+
return tokens;
|
|
3752
|
+
}
|
|
3753
|
+
function splitNameTokens(name) {
|
|
3754
|
+
if (!name) {
|
|
3755
|
+
return /* @__PURE__ */ new Set();
|
|
3756
|
+
}
|
|
3757
|
+
const lowered = name.toLowerCase();
|
|
3758
|
+
const cache = rankingNameTokenCache.get(lowered);
|
|
3759
|
+
if (cache) {
|
|
3760
|
+
return cache;
|
|
3761
|
+
}
|
|
3762
|
+
const tokens = new Set(
|
|
3763
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1)
|
|
3764
|
+
);
|
|
3765
|
+
setBoundedCache(rankingNameTokenCache, lowered, tokens);
|
|
3766
|
+
return tokens;
|
|
3767
|
+
}
|
|
3768
|
+
function chunkTypeBoost(chunkType) {
|
|
3769
|
+
switch (chunkType) {
|
|
3770
|
+
case "function":
|
|
3771
|
+
case "function_declaration":
|
|
3772
|
+
case "method":
|
|
3773
|
+
case "method_definition":
|
|
3774
|
+
case "class":
|
|
3775
|
+
case "class_declaration":
|
|
3776
|
+
return 0.2;
|
|
3777
|
+
case "interface":
|
|
3778
|
+
case "type":
|
|
3779
|
+
case "enum":
|
|
3780
|
+
case "struct":
|
|
3781
|
+
case "impl":
|
|
3782
|
+
case "trait":
|
|
3783
|
+
case "module":
|
|
3784
|
+
return 0.1;
|
|
3785
|
+
default:
|
|
3786
|
+
return 0;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
function isTestOrDocPath(filePath) {
|
|
3790
|
+
return TEST_PATH_SEGMENTS.some((segment) => filePath.includes(segment));
|
|
3791
|
+
}
|
|
3792
|
+
function isLikelyImplementationPath(filePath) {
|
|
3793
|
+
const lowered = filePath.toLowerCase();
|
|
3794
|
+
if (IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS.some((segment) => lowered.includes(segment))) {
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3797
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
3798
|
+
if (["md", "mdx", "txt", "rst", "adoc", "snap", "json", "yaml", "yml", "lock"].includes(ext)) {
|
|
3799
|
+
return false;
|
|
3800
|
+
}
|
|
3801
|
+
return true;
|
|
3802
|
+
}
|
|
3803
|
+
function classifyQueryIntent(tokens) {
|
|
3804
|
+
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
3805
|
+
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
3806
|
+
return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
|
|
3807
|
+
}
|
|
3808
|
+
function classifyQueryIntentRaw(query) {
|
|
3809
|
+
const lowerQuery = query.toLowerCase();
|
|
3810
|
+
const docTestRawHits = Array.from(DOC_TEST_INTENT_HINTS).filter(
|
|
3811
|
+
(hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)
|
|
3812
|
+
).length;
|
|
3813
|
+
const sourceRawHits = [
|
|
3814
|
+
"implement",
|
|
3815
|
+
"implementation",
|
|
3816
|
+
"implements",
|
|
3817
|
+
"function",
|
|
3818
|
+
"method",
|
|
3819
|
+
"class",
|
|
3820
|
+
"logic",
|
|
3821
|
+
"algorithm",
|
|
3822
|
+
"pipeline",
|
|
3823
|
+
"indexer"
|
|
3824
|
+
].filter((hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)).length;
|
|
3825
|
+
if (docTestRawHits > sourceRawHits) {
|
|
3826
|
+
return "doc_test";
|
|
3827
|
+
}
|
|
3828
|
+
if (sourceRawHits > docTestRawHits) {
|
|
3829
|
+
return "source";
|
|
3830
|
+
}
|
|
3831
|
+
const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
|
|
3832
|
+
const hasIdentifierHints = extractIdentifierHints(query).length > 0;
|
|
3833
|
+
if (hasWhereIsPattern && hasIdentifierHints && docTestRawHits === 0) {
|
|
3834
|
+
return "source";
|
|
3835
|
+
}
|
|
3836
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
3837
|
+
return classifyQueryIntent(queryTokens);
|
|
3838
|
+
}
|
|
3839
|
+
function classifyDocIntent(tokens) {
|
|
3840
|
+
const docHits = tokens.filter((t) => DOC_INTENT_HINTS.has(t)).length;
|
|
3841
|
+
const testHits = tokens.filter((t) => ["test", "tests", "fixture", "fixtures", "benchmark"].includes(t)).length;
|
|
3842
|
+
if (docHits > 0 && testHits === 0) return "docs";
|
|
3843
|
+
if (testHits > 0 && docHits === 0) return "test";
|
|
3844
|
+
if (testHits > 0 || docHits > 0) return "mixed";
|
|
3845
|
+
return "none";
|
|
3846
|
+
}
|
|
3847
|
+
function isImplementationChunkType(chunkType) {
|
|
3848
|
+
return [
|
|
3849
|
+
"export_statement",
|
|
3850
|
+
"function",
|
|
3851
|
+
"function_declaration",
|
|
3852
|
+
"method",
|
|
3853
|
+
"method_definition",
|
|
3854
|
+
"class",
|
|
3855
|
+
"class_declaration",
|
|
3856
|
+
"interface",
|
|
3857
|
+
"type",
|
|
3858
|
+
"enum",
|
|
3859
|
+
"module"
|
|
3860
|
+
].includes(chunkType);
|
|
3861
|
+
}
|
|
3862
|
+
function extractIdentifierHints(query) {
|
|
3863
|
+
const identifiers = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3864
|
+
return identifiers.filter((id) => id.length >= 3).filter((id) => {
|
|
3865
|
+
const lower = id.toLowerCase();
|
|
3866
|
+
if (STOPWORDS.has(lower)) return false;
|
|
3867
|
+
return /[A-Z]/.test(id) || id.includes("_") || id.endsWith("Results") || id.endsWith("Result");
|
|
3868
|
+
}).map((id) => id.toLowerCase());
|
|
3869
|
+
}
|
|
3870
|
+
function extractCodeTermHints(query) {
|
|
3871
|
+
const terms = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3872
|
+
return terms.map((term) => term.toLowerCase()).filter((term) => term.length >= 3).filter((term) => !STOPWORDS.has(term));
|
|
3873
|
+
}
|
|
3874
|
+
function normalizeIdentifierVariants(identifier) {
|
|
3875
|
+
const lower = identifier.toLowerCase();
|
|
3876
|
+
const compact = lower.replace(/[^a-z0-9]/g, "");
|
|
3877
|
+
const snake = compact.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
3878
|
+
const kebab = snake.replace(/_/g, "-");
|
|
3879
|
+
const variants = [lower, compact, snake, kebab].filter((value) => value.length > 0);
|
|
3880
|
+
return Array.from(new Set(variants));
|
|
3881
|
+
}
|
|
3882
|
+
function scoreIdentifierMatch(name, filePath, hints) {
|
|
3883
|
+
const nameLower = (name ?? "").toLowerCase();
|
|
3884
|
+
const pathLower = filePath.toLowerCase();
|
|
3885
|
+
let best = 0;
|
|
3886
|
+
for (const hint of hints) {
|
|
3887
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3888
|
+
for (const variant of variants) {
|
|
3889
|
+
if (nameLower === variant) {
|
|
3890
|
+
best = Math.max(best, 1);
|
|
3891
|
+
} else if (nameLower.includes(variant)) {
|
|
3892
|
+
best = Math.max(best, 0.8);
|
|
3893
|
+
} else if (pathLower.includes(variant)) {
|
|
3894
|
+
best = Math.max(best, 0.6);
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
return best;
|
|
3899
|
+
}
|
|
3900
|
+
function extractPrimaryIdentifierQueryHint(query) {
|
|
3901
|
+
const identifiers = extractIdentifierHints(query);
|
|
3902
|
+
if (identifiers.length > 0) {
|
|
3903
|
+
return identifiers[0] ?? null;
|
|
3904
|
+
}
|
|
3905
|
+
const codeTerms = extractCodeTermHints(query);
|
|
3906
|
+
const best = codeTerms.find((term) => term.length >= 6);
|
|
3907
|
+
return best ?? null;
|
|
3908
|
+
}
|
|
3909
|
+
var FILE_PATH_HINT_EXTENSIONS = [
|
|
3910
|
+
"ts",
|
|
3911
|
+
"tsx",
|
|
3912
|
+
"js",
|
|
3913
|
+
"jsx",
|
|
3914
|
+
"mjs",
|
|
3915
|
+
"cjs",
|
|
3916
|
+
"mts",
|
|
3917
|
+
"cts",
|
|
3918
|
+
"py",
|
|
3919
|
+
"rs",
|
|
3920
|
+
"go",
|
|
3921
|
+
"java",
|
|
3922
|
+
"kt",
|
|
3923
|
+
"kts",
|
|
3924
|
+
"swift",
|
|
3925
|
+
"rb",
|
|
3926
|
+
"php",
|
|
3927
|
+
"c",
|
|
3928
|
+
"h",
|
|
3929
|
+
"cc",
|
|
3930
|
+
"cpp",
|
|
3931
|
+
"cxx",
|
|
3932
|
+
"hpp",
|
|
3933
|
+
"cs",
|
|
3934
|
+
"scala",
|
|
3935
|
+
"lua",
|
|
3936
|
+
"sh",
|
|
3937
|
+
"bash",
|
|
3938
|
+
"zsh",
|
|
3939
|
+
"json",
|
|
3940
|
+
"yaml",
|
|
3941
|
+
"yml",
|
|
3942
|
+
"toml"
|
|
3943
|
+
];
|
|
3944
|
+
var FILE_PATH_HINT_SUFFIX_REGEX = new RegExp(
|
|
3945
|
+
"\\s+\\bin\\s+[\"'`]?((?:\\.\\/)?(?:[A-Za-z0-9._-]+\\/)+[A-Za-z0-9._-]+\\.(?:" + FILE_PATH_HINT_EXTENSIONS.join("|") + "))[\"'`]?[\\])}>.,;!?]*\\s*$",
|
|
3946
|
+
"i"
|
|
3947
|
+
);
|
|
3948
|
+
function normalizeFilePathForHintMatch(filePath) {
|
|
3949
|
+
return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
3950
|
+
}
|
|
3951
|
+
function pathMatchesHint(filePath, hint) {
|
|
3952
|
+
const normalizedPath = normalizeFilePathForHintMatch(filePath);
|
|
3953
|
+
const normalizedHint = normalizeFilePathForHintMatch(hint);
|
|
3954
|
+
return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
|
|
3955
|
+
}
|
|
3956
|
+
function extractFilePathHint(query) {
|
|
3957
|
+
const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
|
|
3958
|
+
const rawPath = match?.[1];
|
|
3959
|
+
if (!rawPath) {
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
return rawPath.replace(/^\.\//, "");
|
|
3963
|
+
}
|
|
3964
|
+
function stripFilePathHint(query) {
|
|
3965
|
+
const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
|
|
3966
|
+
return stripped.length > 0 ? stripped : query;
|
|
3967
|
+
}
|
|
3968
|
+
function buildDeterministicIdentifierPass(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
3969
|
+
if (!prioritizeSourcePaths) {
|
|
3970
|
+
return [];
|
|
3971
|
+
}
|
|
3972
|
+
const primary = extractPrimaryIdentifierQueryHint(query);
|
|
3973
|
+
if (!primary) {
|
|
3974
|
+
return [];
|
|
3975
|
+
}
|
|
3976
|
+
const filePathHint = extractFilePathHint(query);
|
|
3977
|
+
const primaryVariants = normalizeIdentifierVariants(primary);
|
|
3978
|
+
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);
|
|
3979
|
+
const deterministic = candidates.filter(
|
|
3980
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
3981
|
+
).map((candidate) => {
|
|
3982
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
3983
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
3984
|
+
let maxMatch = 0;
|
|
3985
|
+
const nameMatchesPrimary = primaryVariants.some(
|
|
3986
|
+
(variant) => nameLower === variant || nameLower.replace(/[^a-z0-9]/g, "") === variant.replace(/[^a-z0-9]/g, "")
|
|
3987
|
+
);
|
|
3988
|
+
const pathMatchesFileHint = filePathHint ? pathMatchesHint(candidate.metadata.filePath, filePathHint) : false;
|
|
3989
|
+
for (const hint of hints) {
|
|
3990
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3991
|
+
for (const variant of variants) {
|
|
3992
|
+
if (nameLower === variant) {
|
|
3993
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
3994
|
+
} else if (nameLower.includes(variant)) {
|
|
3995
|
+
maxMatch = Math.max(maxMatch, 0.85);
|
|
3996
|
+
} else if (pathLower.includes(variant)) {
|
|
3997
|
+
maxMatch = Math.max(maxMatch, 0.7);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
if (pathMatchesFileHint && nameMatchesPrimary) {
|
|
4002
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
4003
|
+
}
|
|
4004
|
+
return {
|
|
4005
|
+
candidate,
|
|
4006
|
+
maxMatch,
|
|
4007
|
+
pathMatchesFileHint,
|
|
4008
|
+
nameMatchesPrimary
|
|
4009
|
+
};
|
|
4010
|
+
}).filter((entry) => entry.maxMatch >= 0.7).sort((a, b) => {
|
|
4011
|
+
const aAnchored = a.pathMatchesFileHint && a.nameMatchesPrimary ? 1 : 0;
|
|
4012
|
+
const bAnchored = b.pathMatchesFileHint && b.nameMatchesPrimary ? 1 : 0;
|
|
4013
|
+
if (aAnchored !== bAnchored) return bAnchored - aAnchored;
|
|
4014
|
+
if (b.maxMatch !== a.maxMatch) return b.maxMatch - a.maxMatch;
|
|
4015
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4016
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4017
|
+
}).slice(0, Math.max(limit * 2, 12));
|
|
4018
|
+
return deterministic.map((entry) => ({
|
|
4019
|
+
id: entry.candidate.id,
|
|
4020
|
+
score: entry.pathMatchesFileHint && entry.nameMatchesPrimary ? 0.995 : Math.min(1, 0.9 + entry.maxMatch * 0.09),
|
|
4021
|
+
metadata: entry.candidate.metadata
|
|
4022
|
+
}));
|
|
4023
|
+
}
|
|
4024
|
+
function fuseResultsWeighted(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4025
|
+
const semanticWeight = 1 - keywordWeight;
|
|
4026
|
+
const fusedScores = /* @__PURE__ */ new Map();
|
|
4027
|
+
for (const r of semanticResults) {
|
|
4028
|
+
fusedScores.set(r.id, {
|
|
4029
|
+
score: r.score * semanticWeight,
|
|
4030
|
+
metadata: r.metadata
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
for (const r of keywordResults) {
|
|
4034
|
+
const existing = fusedScores.get(r.id);
|
|
4035
|
+
if (existing) {
|
|
4036
|
+
existing.score += r.score * keywordWeight;
|
|
4037
|
+
} else {
|
|
4038
|
+
fusedScores.set(r.id, {
|
|
4039
|
+
score: r.score * keywordWeight,
|
|
4040
|
+
metadata: r.metadata
|
|
4041
|
+
});
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4045
|
+
id,
|
|
4046
|
+
score: data.score,
|
|
4047
|
+
metadata: data.metadata
|
|
4048
|
+
}));
|
|
4049
|
+
results.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4050
|
+
return results.slice(0, limit);
|
|
4051
|
+
}
|
|
4052
|
+
function fuseResultsRrf(semanticResults, keywordResults, rrfK, limit) {
|
|
4053
|
+
const maxPossibleRaw = 2 / (rrfK + 1);
|
|
4054
|
+
const rankByIdSemantic = /* @__PURE__ */ new Map();
|
|
4055
|
+
const rankByIdKeyword = /* @__PURE__ */ new Map();
|
|
4056
|
+
const metadataById = /* @__PURE__ */ new Map();
|
|
4057
|
+
semanticResults.forEach((result, index) => {
|
|
4058
|
+
rankByIdSemantic.set(result.id, index + 1);
|
|
4059
|
+
metadataById.set(result.id, result.metadata);
|
|
4060
|
+
});
|
|
4061
|
+
keywordResults.forEach((result, index) => {
|
|
4062
|
+
rankByIdKeyword.set(result.id, index + 1);
|
|
4063
|
+
if (!metadataById.has(result.id)) {
|
|
4064
|
+
metadataById.set(result.id, result.metadata);
|
|
4065
|
+
}
|
|
4066
|
+
});
|
|
4067
|
+
const allIds = /* @__PURE__ */ new Set([...rankByIdSemantic.keys(), ...rankByIdKeyword.keys()]);
|
|
4068
|
+
const fused = [];
|
|
4069
|
+
for (const id of allIds) {
|
|
4070
|
+
const semanticRank = rankByIdSemantic.get(id);
|
|
4071
|
+
const keywordRank = rankByIdKeyword.get(id);
|
|
4072
|
+
const semanticScore = semanticRank ? 1 / (rrfK + semanticRank) : 0;
|
|
4073
|
+
const keywordScore = keywordRank ? 1 / (rrfK + keywordRank) : 0;
|
|
4074
|
+
const metadata = metadataById.get(id);
|
|
4075
|
+
if (!metadata) continue;
|
|
4076
|
+
fused.push({
|
|
4077
|
+
id,
|
|
4078
|
+
score: maxPossibleRaw > 0 ? (semanticScore + keywordScore) / maxPossibleRaw : 0,
|
|
4079
|
+
metadata
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
fused.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4083
|
+
return fused.slice(0, limit);
|
|
4084
|
+
}
|
|
4085
|
+
function rerankResults(query, candidates, rerankTopN, options) {
|
|
4086
|
+
if (rerankTopN <= 0 || candidates.length <= 1) {
|
|
4087
|
+
return candidates;
|
|
4088
|
+
}
|
|
4089
|
+
const topN = Math.min(rerankTopN, candidates.length);
|
|
4090
|
+
const queryTokens = tokenizeTextForRanking(query);
|
|
4091
|
+
if (queryTokens.size === 0) {
|
|
4092
|
+
return candidates;
|
|
4093
|
+
}
|
|
4094
|
+
const queryTokenList = Array.from(queryTokens);
|
|
4095
|
+
const intent = classifyQueryIntentRaw(query);
|
|
4096
|
+
const docIntent = classifyDocIntent(queryTokenList);
|
|
4097
|
+
const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
|
|
4098
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4099
|
+
const head = candidates.slice(0, topN).map((candidate, idx) => {
|
|
4100
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4101
|
+
const nameTokens = splitNameTokens(candidate.metadata.name ?? "");
|
|
4102
|
+
const chunkTypeTokens = tokenizeTextForRanking(candidate.metadata.chunkType);
|
|
4103
|
+
let exactOrPrefixNameHits = 0;
|
|
4104
|
+
let pathOverlap = 0;
|
|
4105
|
+
let chunkTypeHits = 0;
|
|
4106
|
+
for (const token of queryTokenList) {
|
|
4107
|
+
if (nameTokens.has(token)) {
|
|
4108
|
+
exactOrPrefixNameHits += 1;
|
|
4109
|
+
} else {
|
|
4110
|
+
for (const nameToken of nameTokens) {
|
|
4111
|
+
if (nameToken.startsWith(token) || token.startsWith(nameToken)) {
|
|
4112
|
+
exactOrPrefixNameHits += 1;
|
|
4113
|
+
break;
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
if (pathTokens.has(token)) {
|
|
4118
|
+
pathOverlap += 1;
|
|
4119
|
+
}
|
|
4120
|
+
if (chunkTypeTokens.has(token)) {
|
|
4121
|
+
chunkTypeHits += 1;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
const likelyTestOrDoc = isTestOrDocPath(candidate.metadata.filePath);
|
|
4125
|
+
const lowerPath = candidate.metadata.filePath.toLowerCase();
|
|
4126
|
+
const lowerName = (candidate.metadata.name ?? "").toLowerCase();
|
|
4127
|
+
const hasIdentifierMatch = identifierHints.some((id) => lowerPath.includes(id) || lowerName.includes(id));
|
|
4128
|
+
const implementationPathBoost = preferSourcePaths && isLikelyImplementationPath(candidate.metadata.filePath) ? 0.08 : 0;
|
|
4129
|
+
const isReadmePath = candidate.metadata.filePath.toLowerCase().includes("readme");
|
|
4130
|
+
const testDocPenalty = preferSourcePaths && likelyTestOrDoc ? 0.12 : 0;
|
|
4131
|
+
const readmeDocBoost = !preferSourcePaths && isReadmePath ? 0.08 : 0;
|
|
4132
|
+
const identifierBoost = hasIdentifierMatch ? 0.12 : 0;
|
|
4133
|
+
const tokenCoverage = queryTokenList.length > 0 ? (exactOrPrefixNameHits + pathOverlap + chunkTypeHits) / queryTokenList.length : 0;
|
|
4134
|
+
const coverageBoost = Math.min(0.12, tokenCoverage * 0.06);
|
|
4135
|
+
const deterministicBoost = exactOrPrefixNameHits * 0.08 + pathOverlap * 0.03 + chunkTypeHits * 0.02 + coverageBoost + identifierBoost + implementationPathBoost - testDocPenalty + readmeDocBoost + chunkTypeBoost(candidate.metadata.chunkType);
|
|
4136
|
+
return {
|
|
4137
|
+
candidate,
|
|
4138
|
+
boostedScore: candidate.score + deterministicBoost,
|
|
4139
|
+
originalIndex: idx,
|
|
4140
|
+
hasIdentifierMatch,
|
|
4141
|
+
implementationChunk: isImplementationChunkType(candidate.metadata.chunkType),
|
|
4142
|
+
isLikelyImplementationPath: isLikelyImplementationPath(candidate.metadata.filePath),
|
|
4143
|
+
isTestOrDocPath: likelyTestOrDoc,
|
|
4144
|
+
isReadmePath
|
|
4145
|
+
};
|
|
4146
|
+
});
|
|
4147
|
+
head.sort((a, b) => {
|
|
4148
|
+
if (b.boostedScore !== a.boostedScore) return b.boostedScore - a.boostedScore;
|
|
4149
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4150
|
+
if (a.originalIndex !== b.originalIndex) return a.originalIndex - b.originalIndex;
|
|
4151
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4152
|
+
});
|
|
4153
|
+
if (preferSourcePaths) {
|
|
4154
|
+
head.sort((a, b) => {
|
|
4155
|
+
const aId = a.hasIdentifierMatch ? 1 : 0;
|
|
4156
|
+
const bId = b.hasIdentifierMatch ? 1 : 0;
|
|
4157
|
+
if (aId !== bId) return bId - aId;
|
|
4158
|
+
const aImpl = a.implementationChunk ? 1 : 0;
|
|
4159
|
+
const bImpl = b.implementationChunk ? 1 : 0;
|
|
4160
|
+
if (aImpl !== bImpl) return bImpl - aImpl;
|
|
4161
|
+
const aImplementationPath = a.isLikelyImplementationPath ? 1 : 0;
|
|
4162
|
+
const bImplementationPath = b.isLikelyImplementationPath ? 1 : 0;
|
|
4163
|
+
if (aImplementationPath !== bImplementationPath) return bImplementationPath - aImplementationPath;
|
|
4164
|
+
const aTestDoc = a.isTestOrDocPath ? 1 : 0;
|
|
4165
|
+
const bTestDoc = b.isTestOrDocPath ? 1 : 0;
|
|
4166
|
+
if (aTestDoc !== bTestDoc) return aTestDoc - bTestDoc;
|
|
4167
|
+
return 0;
|
|
4168
|
+
});
|
|
4169
|
+
} else if (docIntent === "docs") {
|
|
4170
|
+
head.sort((a, b) => {
|
|
4171
|
+
const aReadme = a.isReadmePath ? 1 : 0;
|
|
4172
|
+
const bReadme = b.isReadmePath ? 1 : 0;
|
|
4173
|
+
if (aReadme !== bReadme) return bReadme - aReadme;
|
|
4174
|
+
return 0;
|
|
4175
|
+
});
|
|
4176
|
+
}
|
|
4177
|
+
const tail = candidates.slice(topN);
|
|
4178
|
+
return [...head.map((entry) => entry.candidate), ...tail];
|
|
4179
|
+
}
|
|
4180
|
+
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4181
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4182
|
+
const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
|
|
4183
|
+
const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
|
|
4184
|
+
const rerankPool = fused.slice(0, rerankPoolLimit);
|
|
4185
|
+
return rerankResults(query, rerankPool, options.rerankTopN, {
|
|
4186
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
|
|
4187
|
+
});
|
|
4188
|
+
}
|
|
4189
|
+
function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
4190
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4191
|
+
const bounded = semanticResults.slice(0, overfetchLimit);
|
|
4192
|
+
return rerankResults(query, bounded, options.rerankTopN, {
|
|
4193
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
|
|
4194
|
+
});
|
|
4195
|
+
}
|
|
4196
|
+
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4197
|
+
if (combined.length === 0) {
|
|
4198
|
+
return combined;
|
|
4199
|
+
}
|
|
4200
|
+
if (!prioritizeSourcePaths) {
|
|
4201
|
+
return combined;
|
|
4202
|
+
}
|
|
4203
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4204
|
+
if (identifierHints.length === 0) {
|
|
4205
|
+
return combined;
|
|
4206
|
+
}
|
|
4207
|
+
const combinedById = new Map(combined.map((candidate) => [candidate.id, candidate]));
|
|
4208
|
+
const candidateUnion = /* @__PURE__ */ new Map();
|
|
4209
|
+
for (const candidate of semanticCandidates) {
|
|
4210
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4211
|
+
}
|
|
4212
|
+
for (const candidate of keywordCandidates) {
|
|
4213
|
+
if (!candidateUnion.has(candidate.id)) {
|
|
4214
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
if (database) {
|
|
4218
|
+
for (const identifier of identifierHints) {
|
|
4219
|
+
const symbols = database.getSymbolsByName(identifier);
|
|
4220
|
+
for (const symbol of symbols) {
|
|
4221
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4222
|
+
for (const chunk of chunks) {
|
|
4223
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4224
|
+
continue;
|
|
4225
|
+
}
|
|
4226
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4227
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4231
|
+
continue;
|
|
4232
|
+
}
|
|
4233
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4234
|
+
continue;
|
|
4235
|
+
}
|
|
4236
|
+
const existing = combinedById.get(chunk.chunkId) ?? candidateUnion.get(chunk.chunkId);
|
|
4237
|
+
const metadata = existing?.metadata ?? {
|
|
4238
|
+
filePath: chunk.filePath,
|
|
4239
|
+
startLine: chunk.startLine,
|
|
4240
|
+
endLine: chunk.endLine,
|
|
4241
|
+
chunkType,
|
|
4242
|
+
name: chunk.name ?? void 0,
|
|
4243
|
+
language: chunk.language,
|
|
4244
|
+
hash: chunk.contentHash
|
|
4245
|
+
};
|
|
4246
|
+
const baselineScore = existing?.score ?? 0.5;
|
|
4247
|
+
candidateUnion.set(chunk.chunkId, {
|
|
4248
|
+
id: chunk.chunkId,
|
|
4249
|
+
score: Math.min(1, baselineScore + 0.5),
|
|
4250
|
+
metadata
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
const promoted = [];
|
|
4257
|
+
for (const candidate of candidateUnion.values()) {
|
|
4258
|
+
const filePathLower = candidate.metadata.filePath.toLowerCase();
|
|
4259
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4260
|
+
const exactIdentifierMatch = identifierHints.some((hint) => nameLower === hint);
|
|
4261
|
+
const hasIdentifierMatch = exactIdentifierMatch || identifierHints.some(
|
|
4262
|
+
(hint) => nameLower.includes(hint) || filePathLower.includes(hint)
|
|
4263
|
+
);
|
|
4264
|
+
if (!hasIdentifierMatch) {
|
|
4265
|
+
continue;
|
|
4266
|
+
}
|
|
4267
|
+
if (!isImplementationChunkType(candidate.metadata.chunkType)) {
|
|
4268
|
+
continue;
|
|
4269
|
+
}
|
|
4270
|
+
if (!isLikelyImplementationPath(candidate.metadata.filePath)) {
|
|
4271
|
+
continue;
|
|
4272
|
+
}
|
|
4273
|
+
const existing = combinedById.get(candidate.id) ?? candidate;
|
|
4274
|
+
const rescueBoost = exactIdentifierMatch ? 0.45 : 0.25;
|
|
4275
|
+
const boostedScore = Math.min(1, Math.max(existing.score, candidate.score) + rescueBoost);
|
|
4276
|
+
promoted.push({
|
|
4277
|
+
id: existing.id,
|
|
4278
|
+
score: boostedScore,
|
|
4279
|
+
metadata: existing.metadata
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4282
|
+
if (promoted.length === 0) {
|
|
4283
|
+
return combined;
|
|
4284
|
+
}
|
|
4285
|
+
promoted.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4286
|
+
const promotedIds = new Set(promoted.map((candidate) => candidate.id));
|
|
4287
|
+
const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
|
|
4288
|
+
return [...promoted, ...remainder];
|
|
4289
|
+
}
|
|
4290
|
+
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4291
|
+
if (!prioritizeSourcePaths) {
|
|
4292
|
+
return [];
|
|
4293
|
+
}
|
|
4294
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4295
|
+
const codeTermHints = extractCodeTermHints(query);
|
|
4296
|
+
if (identifierHints.length === 0 && codeTermHints.length === 0) {
|
|
4297
|
+
return [];
|
|
4298
|
+
}
|
|
4299
|
+
const symbolCandidates = /* @__PURE__ */ new Map();
|
|
4300
|
+
const filePathHint = extractFilePathHint(query);
|
|
4301
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4302
|
+
const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
|
|
4303
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4304
|
+
return;
|
|
4305
|
+
}
|
|
4306
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4307
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const nameLower = (chunk.name ?? "").toLowerCase();
|
|
4314
|
+
const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
|
|
4315
|
+
const base = baseScore ?? (exactName ? 0.99 : 0.88);
|
|
4316
|
+
const existing = symbolCandidates.get(chunk.chunkId);
|
|
4317
|
+
if (!existing || base > existing.score) {
|
|
4318
|
+
symbolCandidates.set(chunk.chunkId, {
|
|
4319
|
+
id: chunk.chunkId,
|
|
4320
|
+
score: base,
|
|
4321
|
+
metadata: {
|
|
4322
|
+
filePath: chunk.filePath,
|
|
4323
|
+
startLine: chunk.startLine,
|
|
4324
|
+
endLine: chunk.endLine,
|
|
4325
|
+
chunkType,
|
|
4326
|
+
name: chunk.name ?? void 0,
|
|
4327
|
+
language: chunk.language,
|
|
4328
|
+
hash: chunk.contentHash
|
|
4329
|
+
}
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
const normalizedHints = identifierHints.flatMap((hint) => [
|
|
4334
|
+
hint,
|
|
4335
|
+
hint.replace(/_/g, ""),
|
|
4336
|
+
hint.replace(/_/g, "-")
|
|
4337
|
+
]).filter((hint, idx, arr) => hint.length >= 3 && arr.indexOf(hint) === idx).slice(0, 6);
|
|
4338
|
+
for (const identifier of normalizedHints) {
|
|
4339
|
+
const symbols = [
|
|
4340
|
+
...database.getSymbolsByName(identifier),
|
|
4341
|
+
...database.getSymbolsByNameCi(identifier)
|
|
4342
|
+
];
|
|
4343
|
+
const chunksByName = [
|
|
4344
|
+
...database.getChunksByName(identifier),
|
|
4345
|
+
...database.getChunksByNameCi(identifier)
|
|
4346
|
+
];
|
|
4347
|
+
const normalizedIdentifier = identifier.replace(/_/g, "");
|
|
4348
|
+
const dedupSymbols = /* @__PURE__ */ new Map();
|
|
4349
|
+
for (const symbol of symbols) {
|
|
4350
|
+
dedupSymbols.set(symbol.id, symbol);
|
|
4351
|
+
}
|
|
4352
|
+
for (const symbol of dedupSymbols.values()) {
|
|
4353
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4354
|
+
for (const chunk of chunks) {
|
|
4355
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4356
|
+
continue;
|
|
4357
|
+
}
|
|
4358
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
const dedupChunksByName = /* @__PURE__ */ new Map();
|
|
4362
|
+
for (const chunk of chunksByName) {
|
|
4363
|
+
dedupChunksByName.set(chunk.chunkId, chunk);
|
|
4364
|
+
}
|
|
4365
|
+
for (const chunk of dedupChunksByName.values()) {
|
|
4366
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
if (filePathHint && primaryHint) {
|
|
4370
|
+
const primaryChunks = [
|
|
4371
|
+
...database.getChunksByName(primaryHint),
|
|
4372
|
+
...database.getChunksByNameCi(primaryHint)
|
|
4373
|
+
];
|
|
4374
|
+
const dedupPrimaryChunks = /* @__PURE__ */ new Map();
|
|
4375
|
+
for (const chunk of primaryChunks) {
|
|
4376
|
+
dedupPrimaryChunks.set(chunk.chunkId, chunk);
|
|
4377
|
+
}
|
|
4378
|
+
for (const chunk of dedupPrimaryChunks.values()) {
|
|
4379
|
+
if (!pathMatchesHint(chunk.filePath, filePathHint)) {
|
|
4380
|
+
continue;
|
|
4381
|
+
}
|
|
4382
|
+
const normalizedPrimary = primaryHint.replace(/_/g, "");
|
|
4383
|
+
upsertChunkCandidate(chunk, primaryHint, normalizedPrimary, 1);
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4387
|
+
if (ranked.length === 0) {
|
|
4388
|
+
const implementationFallback = fallbackCandidates.filter(
|
|
4389
|
+
(candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath(candidate.metadata.filePath)
|
|
4390
|
+
);
|
|
4391
|
+
for (const candidate of implementationFallback) {
|
|
4392
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4393
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
4394
|
+
const exactHintMatch = normalizedHints.some((hint) => nameLower === hint || nameLower.replace(/_/g, "") === hint.replace(/_/g, ""));
|
|
4395
|
+
const tokenizedName = tokenizeTextForRanking(nameLower);
|
|
4396
|
+
const tokenHits = codeTermHints.filter((term) => tokenizedName.has(term) || pathLower.includes(term)).length;
|
|
4397
|
+
if (!exactHintMatch && tokenHits === 0) {
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
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));
|
|
4401
|
+
symbolCandidates.set(candidate.id, {
|
|
4402
|
+
id: candidate.id,
|
|
4403
|
+
score: laneScore,
|
|
4404
|
+
metadata: candidate.metadata
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
if (symbolCandidates.size === 0) {
|
|
4408
|
+
const queryTokenSet = tokenizeTextForRanking(query);
|
|
4409
|
+
const rankedFallback = implementationFallback.map((candidate) => {
|
|
4410
|
+
const nameTokens = tokenizeTextForRanking(candidate.metadata.name ?? "");
|
|
4411
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4412
|
+
let overlap = 0;
|
|
4413
|
+
for (const token of queryTokenSet) {
|
|
4414
|
+
if (nameTokens.has(token) || pathTokens.has(token)) {
|
|
4415
|
+
overlap += 1;
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
const overlapScore = queryTokenSet.size > 0 ? overlap / queryTokenSet.size : 0;
|
|
4419
|
+
return {
|
|
4420
|
+
candidate,
|
|
4421
|
+
overlapScore
|
|
4422
|
+
};
|
|
4423
|
+
}).filter((entry) => entry.overlapScore > 0).sort((a, b) => b.overlapScore - a.overlapScore || b.candidate.score - a.candidate.score).slice(0, Math.max(limit, 3));
|
|
4424
|
+
for (const entry of rankedFallback) {
|
|
4425
|
+
symbolCandidates.set(entry.candidate.id, {
|
|
4426
|
+
id: entry.candidate.id,
|
|
4427
|
+
score: Math.min(0.94, Math.max(entry.candidate.score, 0.8 + entry.overlapScore * 0.1)),
|
|
4428
|
+
metadata: entry.candidate.metadata
|
|
4429
|
+
});
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4434
|
+
return withFallback.slice(0, Math.max(limit * 2, limit));
|
|
4435
|
+
}
|
|
4436
|
+
function buildIdentifierDefinitionLane(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4437
|
+
if (!prioritizeSourcePaths) {
|
|
4438
|
+
return [];
|
|
4439
|
+
}
|
|
4440
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4441
|
+
if (!primaryHint) {
|
|
4442
|
+
return [];
|
|
4443
|
+
}
|
|
4444
|
+
const hints = [primaryHint, ...extractIdentifierHints(query), ...extractCodeTermHints(query)].slice(0, 8);
|
|
4445
|
+
const scored = candidates.filter(
|
|
4446
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
4447
|
+
).map((candidate) => {
|
|
4448
|
+
const matchScore = scoreIdentifierMatch(candidate.metadata.name, candidate.metadata.filePath, hints);
|
|
4449
|
+
return {
|
|
4450
|
+
candidate,
|
|
4451
|
+
matchScore
|
|
4452
|
+
};
|
|
4453
|
+
}).filter((entry) => entry.matchScore > 0).sort((a, b) => {
|
|
4454
|
+
if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
|
|
4455
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4456
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4457
|
+
}).slice(0, Math.max(limit * 2, 10));
|
|
4458
|
+
return scored.map((entry) => ({
|
|
4459
|
+
id: entry.candidate.id,
|
|
4460
|
+
score: Math.min(1, 0.9 + entry.matchScore * 0.09),
|
|
4461
|
+
metadata: entry.candidate.metadata
|
|
4462
|
+
}));
|
|
4463
|
+
}
|
|
4464
|
+
function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
4465
|
+
if (symbolLane.length === 0) {
|
|
4466
|
+
return hybridLane.slice(0, limit);
|
|
4467
|
+
}
|
|
4468
|
+
const out = [];
|
|
4469
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4470
|
+
for (const candidate of symbolLane) {
|
|
4471
|
+
if (seen.has(candidate.id)) continue;
|
|
4472
|
+
out.push(candidate);
|
|
4473
|
+
seen.add(candidate.id);
|
|
4474
|
+
if (out.length >= limit) return out;
|
|
4475
|
+
}
|
|
4476
|
+
for (const candidate of hybridLane) {
|
|
4477
|
+
if (seen.has(candidate.id)) continue;
|
|
4478
|
+
out.push(candidate);
|
|
4479
|
+
seen.add(candidate.id);
|
|
4480
|
+
if (out.length >= limit) return out;
|
|
4481
|
+
}
|
|
4482
|
+
return out;
|
|
4483
|
+
}
|
|
4484
|
+
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
4485
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4486
|
+
for (const candidate of semanticCandidates) {
|
|
4487
|
+
byId.set(candidate.id, candidate);
|
|
4488
|
+
}
|
|
4489
|
+
for (const candidate of keywordCandidates) {
|
|
4490
|
+
const existing = byId.get(candidate.id);
|
|
4491
|
+
if (!existing || candidate.score > existing.score) {
|
|
4492
|
+
byId.set(candidate.id, candidate);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
return Array.from(byId.values());
|
|
4496
|
+
}
|
|
3485
4497
|
var Indexer = class {
|
|
3486
4498
|
config;
|
|
3487
4499
|
projectRoot;
|
|
@@ -3984,6 +4996,79 @@ var Indexer = class {
|
|
|
3984
4996
|
if (chunkDataBatch.length > 0) {
|
|
3985
4997
|
database.upsertChunksBatch(chunkDataBatch);
|
|
3986
4998
|
}
|
|
4999
|
+
const allSymbolIds = /* @__PURE__ */ new Set();
|
|
5000
|
+
const symbolsByFile = /* @__PURE__ */ new Map();
|
|
5001
|
+
for (let i = 0; i < parsedFiles.length; i++) {
|
|
5002
|
+
const parsed = parsedFiles[i];
|
|
5003
|
+
const changedFile = changedFiles[i];
|
|
5004
|
+
database.deleteCallEdgesByFile(parsed.path);
|
|
5005
|
+
database.deleteSymbolsByFile(parsed.path);
|
|
5006
|
+
const fileSymbols = [];
|
|
5007
|
+
for (const chunk of parsed.chunks) {
|
|
5008
|
+
if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
|
|
5009
|
+
const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
|
|
5010
|
+
const symbol = {
|
|
5011
|
+
id: symbolId,
|
|
5012
|
+
filePath: parsed.path,
|
|
5013
|
+
name: chunk.name,
|
|
5014
|
+
kind: chunk.chunkType,
|
|
5015
|
+
startLine: chunk.startLine,
|
|
5016
|
+
startCol: 0,
|
|
5017
|
+
endLine: chunk.endLine,
|
|
5018
|
+
endCol: 0,
|
|
5019
|
+
language: chunk.language
|
|
5020
|
+
};
|
|
5021
|
+
fileSymbols.push(symbol);
|
|
5022
|
+
allSymbolIds.add(symbolId);
|
|
5023
|
+
}
|
|
5024
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
5025
|
+
for (const symbol of fileSymbols) {
|
|
5026
|
+
const existing = symbolsByName.get(symbol.name) ?? [];
|
|
5027
|
+
existing.push(symbol);
|
|
5028
|
+
symbolsByName.set(symbol.name, existing);
|
|
5029
|
+
}
|
|
5030
|
+
if (fileSymbols.length > 0) {
|
|
5031
|
+
database.upsertSymbolsBatch(fileSymbols);
|
|
5032
|
+
symbolsByFile.set(parsed.path, fileSymbols);
|
|
5033
|
+
}
|
|
5034
|
+
const fileLanguage = parsed.chunks[0]?.language;
|
|
5035
|
+
if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
|
|
5036
|
+
const callSites = extractCalls(changedFile.content, fileLanguage);
|
|
5037
|
+
if (callSites.length === 0) continue;
|
|
5038
|
+
const edges = [];
|
|
5039
|
+
for (const site of callSites) {
|
|
5040
|
+
const enclosingSymbol = fileSymbols.find(
|
|
5041
|
+
(sym) => site.line >= sym.startLine && site.line <= sym.endLine
|
|
5042
|
+
);
|
|
5043
|
+
if (!enclosingSymbol) continue;
|
|
5044
|
+
const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
|
|
5045
|
+
edges.push({
|
|
5046
|
+
id: edgeId,
|
|
5047
|
+
fromSymbolId: enclosingSymbol.id,
|
|
5048
|
+
targetName: site.calleeName,
|
|
5049
|
+
toSymbolId: void 0,
|
|
5050
|
+
callType: site.callType,
|
|
5051
|
+
line: site.line,
|
|
5052
|
+
col: site.column,
|
|
5053
|
+
isResolved: false
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
if (edges.length > 0) {
|
|
5057
|
+
database.upsertCallEdgesBatch(edges);
|
|
5058
|
+
for (const edge of edges) {
|
|
5059
|
+
const candidates = symbolsByName.get(edge.targetName);
|
|
5060
|
+
if (candidates && candidates.length === 1) {
|
|
5061
|
+
database.resolveCallEdge(edge.id, candidates[0].id);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
for (const filePath of unchangedFilePaths) {
|
|
5067
|
+
const existingSymbols = database.getSymbolsByFile(filePath);
|
|
5068
|
+
for (const sym of existingSymbols) {
|
|
5069
|
+
allSymbolIds.add(sym.id);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
3987
5072
|
let removedCount = 0;
|
|
3988
5073
|
for (const [chunkId] of existingChunks) {
|
|
3989
5074
|
if (!currentChunkIds.has(chunkId)) {
|
|
@@ -4005,6 +5090,8 @@ var Indexer = class {
|
|
|
4005
5090
|
if (pendingChunks.length === 0 && removedCount === 0) {
|
|
4006
5091
|
database.clearBranch(this.currentBranch);
|
|
4007
5092
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5093
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5094
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4008
5095
|
this.fileHashCache = currentFileHashes;
|
|
4009
5096
|
this.saveFileHashCache();
|
|
4010
5097
|
stats.durationMs = Date.now() - startTime;
|
|
@@ -4021,6 +5108,8 @@ var Indexer = class {
|
|
|
4021
5108
|
if (pendingChunks.length === 0) {
|
|
4022
5109
|
database.clearBranch(this.currentBranch);
|
|
4023
5110
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5111
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5112
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4024
5113
|
store.save();
|
|
4025
5114
|
invertedIndex.save();
|
|
4026
5115
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4161,6 +5250,8 @@ var Indexer = class {
|
|
|
4161
5250
|
});
|
|
4162
5251
|
database.clearBranch(this.currentBranch);
|
|
4163
5252
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5253
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5254
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4164
5255
|
store.save();
|
|
4165
5256
|
invertedIndex.save();
|
|
4166
5257
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4271,15 +5362,23 @@ var Indexer = class {
|
|
|
4271
5362
|
}
|
|
4272
5363
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
4273
5364
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
5365
|
+
const fusionStrategy = this.config.search.fusionStrategy;
|
|
5366
|
+
const rrfK = this.config.search.rrfK;
|
|
5367
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
4274
5368
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5369
|
+
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
4275
5370
|
this.logger.search("debug", "Starting search", {
|
|
4276
5371
|
query,
|
|
4277
5372
|
maxResults,
|
|
4278
5373
|
hybridWeight,
|
|
5374
|
+
fusionStrategy,
|
|
5375
|
+
rrfK,
|
|
5376
|
+
rerankTopN,
|
|
4279
5377
|
filterByBranch
|
|
4280
5378
|
});
|
|
4281
5379
|
const embeddingStartTime = import_perf_hooks.performance.now();
|
|
4282
|
-
const
|
|
5380
|
+
const embeddingQuery = stripFilePathHint(query);
|
|
5381
|
+
const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
4283
5382
|
const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
|
|
4284
5383
|
const vectorStartTime = import_perf_hooks.performance.now();
|
|
4285
5384
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
@@ -4287,16 +5386,78 @@ var Indexer = class {
|
|
|
4287
5386
|
const keywordStartTime = import_perf_hooks.performance.now();
|
|
4288
5387
|
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
4289
5388
|
const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
|
|
4290
|
-
const fusionStartTime = import_perf_hooks.performance.now();
|
|
4291
|
-
const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
|
|
4292
|
-
const fusionMs = import_perf_hooks.performance.now() - fusionStartTime;
|
|
4293
5389
|
let branchChunkIds = null;
|
|
4294
5390
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4295
5391
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4296
5392
|
}
|
|
4297
|
-
const
|
|
5393
|
+
const prefilterStartTime = import_perf_hooks.performance.now();
|
|
5394
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5395
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5396
|
+
const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
|
|
5397
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5398
|
+
const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
|
|
5399
|
+
const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
|
|
5400
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5401
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5402
|
+
branch: this.currentBranch
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
5405
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5406
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5407
|
+
branch: this.currentBranch
|
|
5408
|
+
});
|
|
5409
|
+
}
|
|
5410
|
+
if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
|
|
5411
|
+
this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
|
|
5412
|
+
branch: this.currentBranch
|
|
5413
|
+
});
|
|
5414
|
+
}
|
|
5415
|
+
const fusionStartTime = import_perf_hooks.performance.now();
|
|
5416
|
+
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
5417
|
+
fusionStrategy,
|
|
5418
|
+
rrfK,
|
|
5419
|
+
rerankTopN,
|
|
5420
|
+
limit: maxResults,
|
|
5421
|
+
hybridWeight,
|
|
5422
|
+
prioritizeSourcePaths: sourceIntent
|
|
5423
|
+
});
|
|
5424
|
+
const fusionMs = import_perf_hooks.performance.now() - fusionStartTime;
|
|
5425
|
+
const rescued = promoteIdentifierMatches(
|
|
5426
|
+
query,
|
|
5427
|
+
combined,
|
|
5428
|
+
semanticCandidates,
|
|
5429
|
+
keywordCandidates,
|
|
5430
|
+
database,
|
|
5431
|
+
branchChunkIds,
|
|
5432
|
+
sourceIntent
|
|
5433
|
+
);
|
|
5434
|
+
const union = unionCandidates(semanticCandidates, keywordCandidates);
|
|
5435
|
+
const deterministicIdentifierLane = buildDeterministicIdentifierPass(
|
|
5436
|
+
query,
|
|
5437
|
+
union,
|
|
5438
|
+
maxResults,
|
|
5439
|
+
sourceIntent
|
|
5440
|
+
);
|
|
5441
|
+
const identifierLane = buildIdentifierDefinitionLane(
|
|
5442
|
+
query,
|
|
5443
|
+
union,
|
|
5444
|
+
maxResults,
|
|
5445
|
+
sourceIntent
|
|
5446
|
+
);
|
|
5447
|
+
const symbolLane = buildSymbolDefinitionLane(
|
|
5448
|
+
query,
|
|
5449
|
+
database,
|
|
5450
|
+
branchChunkIds,
|
|
5451
|
+
maxResults,
|
|
5452
|
+
union,
|
|
5453
|
+
sourceIntent
|
|
5454
|
+
);
|
|
5455
|
+
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5456
|
+
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5457
|
+
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5458
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
|
|
5459
|
+
const baseFiltered = tiered.filter((r) => {
|
|
4298
5460
|
if (r.score < this.config.search.minScore) return false;
|
|
4299
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4300
5461
|
if (options?.fileType) {
|
|
4301
5462
|
const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
|
|
4302
5463
|
if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
|
|
@@ -4309,7 +5470,11 @@ var Indexer = class {
|
|
|
4309
5470
|
if (r.metadata.chunkType !== options.chunkType) return false;
|
|
4310
5471
|
}
|
|
4311
5472
|
return true;
|
|
4312
|
-
})
|
|
5473
|
+
});
|
|
5474
|
+
const implementationOnly = baseFiltered.filter(
|
|
5475
|
+
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5476
|
+
);
|
|
5477
|
+
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
4313
5478
|
const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
|
|
4314
5479
|
this.logger.recordSearch(totalSearchMs, {
|
|
4315
5480
|
embeddingMs,
|
|
@@ -4324,6 +5489,7 @@ var Indexer = class {
|
|
|
4324
5489
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4325
5490
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
4326
5491
|
keywordMs: Math.round(keywordMs * 100) / 100,
|
|
5492
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100,
|
|
4327
5493
|
fusionMs: Math.round(fusionMs * 100) / 100
|
|
4328
5494
|
});
|
|
4329
5495
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
@@ -4377,34 +5543,6 @@ var Indexer = class {
|
|
|
4377
5543
|
results.sort((a, b) => b.score - a.score);
|
|
4378
5544
|
return results.slice(0, limit);
|
|
4379
5545
|
}
|
|
4380
|
-
fuseResults(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4381
|
-
const semanticWeight = 1 - keywordWeight;
|
|
4382
|
-
const fusedScores = /* @__PURE__ */ new Map();
|
|
4383
|
-
for (const r of semanticResults) {
|
|
4384
|
-
fusedScores.set(r.id, {
|
|
4385
|
-
score: r.score * semanticWeight,
|
|
4386
|
-
metadata: r.metadata
|
|
4387
|
-
});
|
|
4388
|
-
}
|
|
4389
|
-
for (const r of keywordResults) {
|
|
4390
|
-
const existing = fusedScores.get(r.id);
|
|
4391
|
-
if (existing) {
|
|
4392
|
-
existing.score += r.score * keywordWeight;
|
|
4393
|
-
} else {
|
|
4394
|
-
fusedScores.set(r.id, {
|
|
4395
|
-
score: r.score * keywordWeight,
|
|
4396
|
-
metadata: r.metadata
|
|
4397
|
-
});
|
|
4398
|
-
}
|
|
4399
|
-
}
|
|
4400
|
-
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4401
|
-
id,
|
|
4402
|
-
score: data.score,
|
|
4403
|
-
metadata: data.metadata
|
|
4404
|
-
}));
|
|
4405
|
-
results.sort((a, b) => b.score - a.score);
|
|
4406
|
-
return results.slice(0, limit);
|
|
4407
|
-
}
|
|
4408
5546
|
async getStatus() {
|
|
4409
5547
|
const { store, configuredProviderInfo } = await this.ensureInitialized();
|
|
4410
5548
|
return {
|
|
@@ -4427,6 +5565,7 @@ var Indexer = class {
|
|
|
4427
5565
|
this.fileHashCache.clear();
|
|
4428
5566
|
this.saveFileHashCache();
|
|
4429
5567
|
database.clearBranch(this.currentBranch);
|
|
5568
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
4430
5569
|
database.deleteMetadata("index.version");
|
|
4431
5570
|
database.deleteMetadata("index.embeddingProvider");
|
|
4432
5571
|
database.deleteMetadata("index.embeddingModel");
|
|
@@ -4455,6 +5594,8 @@ var Indexer = class {
|
|
|
4455
5594
|
removedCount++;
|
|
4456
5595
|
}
|
|
4457
5596
|
database.deleteChunksByFile(filePath);
|
|
5597
|
+
database.deleteCallEdgesByFile(filePath);
|
|
5598
|
+
database.deleteSymbolsByFile(filePath);
|
|
4458
5599
|
removedFilePaths.push(filePath);
|
|
4459
5600
|
}
|
|
4460
5601
|
}
|
|
@@ -4464,6 +5605,8 @@ var Indexer = class {
|
|
|
4464
5605
|
}
|
|
4465
5606
|
const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
|
|
4466
5607
|
const gcOrphanChunks = database.gcOrphanChunks();
|
|
5608
|
+
const gcOrphanSymbols = database.gcOrphanSymbols();
|
|
5609
|
+
const gcOrphanCallEdges = database.gcOrphanCallEdges();
|
|
4467
5610
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
4468
5611
|
this.logger.gc("info", "Health check complete", {
|
|
4469
5612
|
removedStale: removedCount,
|
|
@@ -4471,7 +5614,7 @@ var Indexer = class {
|
|
|
4471
5614
|
orphanChunks: gcOrphanChunks,
|
|
4472
5615
|
removedFiles: removedFilePaths.length
|
|
4473
5616
|
});
|
|
4474
|
-
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
|
|
5617
|
+
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
4475
5618
|
}
|
|
4476
5619
|
async retryFailedBatches() {
|
|
4477
5620
|
const { store, provider, invertedIndex } = await this.ensureInitialized();
|
|
@@ -4577,9 +5720,29 @@ var Indexer = class {
|
|
|
4577
5720
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4578
5721
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4579
5722
|
}
|
|
4580
|
-
const
|
|
5723
|
+
const prefilterStartTime = import_perf_hooks.performance.now();
|
|
5724
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5725
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5726
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5727
|
+
const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
|
|
5728
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5729
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5730
|
+
branch: this.currentBranch
|
|
5731
|
+
});
|
|
5732
|
+
}
|
|
5733
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5734
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5735
|
+
branch: this.currentBranch
|
|
5736
|
+
});
|
|
5737
|
+
}
|
|
5738
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
5739
|
+
const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
|
|
5740
|
+
rerankTopN,
|
|
5741
|
+
limit,
|
|
5742
|
+
prioritizeSourcePaths: false
|
|
5743
|
+
});
|
|
5744
|
+
const filtered = ranked.filter((r) => {
|
|
4581
5745
|
if (r.score < this.config.search.minScore) return false;
|
|
4582
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4583
5746
|
if (options?.excludeFile) {
|
|
4584
5747
|
if (r.metadata.filePath === options.excludeFile) return false;
|
|
4585
5748
|
}
|
|
@@ -4608,7 +5771,8 @@ var Indexer = class {
|
|
|
4608
5771
|
results: filtered.length,
|
|
4609
5772
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
4610
5773
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4611
|
-
vectorMs: Math.round(vectorMs * 100) / 100
|
|
5774
|
+
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
5775
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100
|
|
4612
5776
|
});
|
|
4613
5777
|
return Promise.all(
|
|
4614
5778
|
filtered.map(async (r) => {
|
|
@@ -4637,6 +5801,14 @@ var Indexer = class {
|
|
|
4637
5801
|
})
|
|
4638
5802
|
);
|
|
4639
5803
|
}
|
|
5804
|
+
async getCallers(targetName) {
|
|
5805
|
+
const { database } = await this.ensureInitialized();
|
|
5806
|
+
return database.getCallersWithContext(targetName, this.currentBranch);
|
|
5807
|
+
}
|
|
5808
|
+
async getCallees(symbolId) {
|
|
5809
|
+
const { database } = await this.ensureInitialized();
|
|
5810
|
+
return database.getCallees(symbolId, this.currentBranch);
|
|
5811
|
+
}
|
|
4640
5812
|
};
|
|
4641
5813
|
|
|
4642
5814
|
// node_modules/chokidar/index.js
|
|
@@ -6695,7 +7867,7 @@ ${formatted.join("\n")}
|
|
|
6695
7867
|
Use Read tool to examine specific files.`;
|
|
6696
7868
|
}
|
|
6697
7869
|
function formatHealthCheck(result) {
|
|
6698
|
-
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
|
|
7870
|
+
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
|
|
6699
7871
|
return "Index is healthy. No stale entries found.";
|
|
6700
7872
|
}
|
|
6701
7873
|
const lines = [`Health check complete:`];
|
|
@@ -6708,6 +7880,12 @@ function formatHealthCheck(result) {
|
|
|
6708
7880
|
if (result.gcOrphanChunks > 0) {
|
|
6709
7881
|
lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
|
|
6710
7882
|
}
|
|
7883
|
+
if (result.gcOrphanSymbols > 0) {
|
|
7884
|
+
lines.push(` Garbage collected orphan symbols: ${result.gcOrphanSymbols}`);
|
|
7885
|
+
}
|
|
7886
|
+
if (result.gcOrphanCallEdges > 0) {
|
|
7887
|
+
lines.push(` Garbage collected orphan call edges: ${result.gcOrphanCallEdges}`);
|
|
7888
|
+
}
|
|
6711
7889
|
if (result.filePaths.length > 0) {
|
|
6712
7890
|
lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
|
|
6713
7891
|
}
|
|
@@ -6722,6 +7900,22 @@ function formatLogs(logs) {
|
|
|
6722
7900
|
return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
|
|
6723
7901
|
}).join("\n");
|
|
6724
7902
|
}
|
|
7903
|
+
function formatDefinitionLookup(results, query) {
|
|
7904
|
+
if (results.length === 0) {
|
|
7905
|
+
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
7906
|
+
}
|
|
7907
|
+
const formatted = results.map((r, idx) => {
|
|
7908
|
+
const header2 = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
7909
|
+
return `${header2} (score: ${r.score.toFixed(2)})
|
|
7910
|
+
\`\`\`
|
|
7911
|
+
${truncateContent(r.content)}
|
|
7912
|
+
\`\`\``;
|
|
7913
|
+
});
|
|
7914
|
+
const header = results.length === 1 ? `Definition found for "${query}":` : `Found ${results.length} definition candidates for "${query}":`;
|
|
7915
|
+
return `${header}
|
|
7916
|
+
|
|
7917
|
+
${formatted.join("\n\n")}`;
|
|
7918
|
+
}
|
|
6725
7919
|
function formatSearchResults(results, scoreFormat = "similarity") {
|
|
6726
7920
|
const formatted = results.map((r, idx) => {
|
|
6727
7921
|
const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
@@ -6907,6 +8101,60 @@ var codebase_search = (0, import_plugin.tool)({
|
|
|
6907
8101
|
${formatSearchResults(results, "score")}`;
|
|
6908
8102
|
}
|
|
6909
8103
|
});
|
|
8104
|
+
var implementation_lookup = (0, import_plugin.tool)({
|
|
8105
|
+
description: "Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s) for a function, class, method, type, or variable. Prefers real implementation files over tests, docs, examples, and fixtures. Use when you need the definition site, not all usages.",
|
|
8106
|
+
args: {
|
|
8107
|
+
query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
8108
|
+
limit: z.number().optional().default(5).describe("Maximum number of results"),
|
|
8109
|
+
fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
8110
|
+
directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
8111
|
+
},
|
|
8112
|
+
async execute(args) {
|
|
8113
|
+
const indexer = getIndexer();
|
|
8114
|
+
const results = await indexer.search(args.query, args.limit ?? 5, {
|
|
8115
|
+
fileType: args.fileType,
|
|
8116
|
+
directory: args.directory,
|
|
8117
|
+
definitionIntent: true
|
|
8118
|
+
});
|
|
8119
|
+
return formatDefinitionLookup(results, args.query);
|
|
8120
|
+
}
|
|
8121
|
+
});
|
|
8122
|
+
var call_graph = (0, import_plugin.tool)({
|
|
8123
|
+
description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
|
|
8124
|
+
args: {
|
|
8125
|
+
name: z.string().describe("Function or method name to query"),
|
|
8126
|
+
direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
8127
|
+
symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
|
|
8128
|
+
},
|
|
8129
|
+
async execute(args) {
|
|
8130
|
+
const indexer = getIndexer();
|
|
8131
|
+
if (args.direction === "callees") {
|
|
8132
|
+
if (!args.symbolId) {
|
|
8133
|
+
return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
|
|
8134
|
+
}
|
|
8135
|
+
const callees = await indexer.getCallees(args.symbolId);
|
|
8136
|
+
if (callees.length === 0) {
|
|
8137
|
+
return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
|
|
8138
|
+
}
|
|
8139
|
+
const formatted2 = callees.map(
|
|
8140
|
+
(e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
|
|
8141
|
+
);
|
|
8142
|
+
return `${args.name} calls ${callees.length} function(s):
|
|
8143
|
+
|
|
8144
|
+
${formatted2.join("\n")}`;
|
|
8145
|
+
}
|
|
8146
|
+
const callers = await indexer.getCallers(args.name);
|
|
8147
|
+
if (callers.length === 0) {
|
|
8148
|
+
return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
|
|
8149
|
+
}
|
|
8150
|
+
const formatted = callers.map(
|
|
8151
|
+
(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]"}`
|
|
8152
|
+
);
|
|
8153
|
+
return `"${args.name}" is called by ${callers.length} function(s):
|
|
8154
|
+
|
|
8155
|
+
${formatted.join("\n")}`;
|
|
8156
|
+
}
|
|
8157
|
+
});
|
|
6910
8158
|
|
|
6911
8159
|
// src/commands/loader.ts
|
|
6912
8160
|
var import_fs5 = require("fs");
|
|
@@ -7011,7 +8259,9 @@ var plugin = async ({ directory }) => {
|
|
|
7011
8259
|
index_health_check,
|
|
7012
8260
|
index_metrics,
|
|
7013
8261
|
index_logs,
|
|
7014
|
-
find_similar
|
|
8262
|
+
find_similar,
|
|
8263
|
+
call_graph,
|
|
8264
|
+
implementation_lookup
|
|
7015
8265
|
},
|
|
7016
8266
|
async config(cfg) {
|
|
7017
8267
|
cfg.command = cfg.command ?? {};
|