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.js
CHANGED
|
@@ -6,13 +6,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var
|
|
10
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
-
}) : x)(function(x) {
|
|
12
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
-
});
|
|
15
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
16
10
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
11
|
};
|
|
18
12
|
var __copyProps = (to, from, except, desc) => {
|
|
@@ -34,7 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
28
|
|
|
35
29
|
// node_modules/eventemitter3/index.js
|
|
36
30
|
var require_eventemitter3 = __commonJS({
|
|
37
|
-
"node_modules/eventemitter3/index.js"(exports,
|
|
31
|
+
"node_modules/eventemitter3/index.js"(exports, module2) {
|
|
38
32
|
"use strict";
|
|
39
33
|
var has = Object.prototype.hasOwnProperty;
|
|
40
34
|
var prefix = "~";
|
|
@@ -188,15 +182,15 @@ var require_eventemitter3 = __commonJS({
|
|
|
188
182
|
EventEmitter3.prototype.addListener = EventEmitter3.prototype.on;
|
|
189
183
|
EventEmitter3.prefixed = prefix;
|
|
190
184
|
EventEmitter3.EventEmitter = EventEmitter3;
|
|
191
|
-
if ("undefined" !== typeof
|
|
192
|
-
|
|
185
|
+
if ("undefined" !== typeof module2) {
|
|
186
|
+
module2.exports = EventEmitter3;
|
|
193
187
|
}
|
|
194
188
|
}
|
|
195
189
|
});
|
|
196
190
|
|
|
197
191
|
// node_modules/ignore/index.js
|
|
198
192
|
var require_ignore = __commonJS({
|
|
199
|
-
"node_modules/ignore/index.js"(exports,
|
|
193
|
+
"node_modules/ignore/index.js"(exports, module2) {
|
|
200
194
|
"use strict";
|
|
201
195
|
function makeArray(subject) {
|
|
202
196
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -645,10 +639,10 @@ var require_ignore = __commonJS({
|
|
|
645
639
|
) {
|
|
646
640
|
setupWindows();
|
|
647
641
|
}
|
|
648
|
-
|
|
642
|
+
module2.exports = factory;
|
|
649
643
|
factory.default = factory;
|
|
650
|
-
|
|
651
|
-
define(
|
|
644
|
+
module2.exports.isPathValid = isPathValid;
|
|
645
|
+
define(module2.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
652
646
|
}
|
|
653
647
|
});
|
|
654
648
|
|
|
@@ -664,7 +658,7 @@ var DEFAULT_INCLUDE = [
|
|
|
664
658
|
"**/*.{py,pyi}",
|
|
665
659
|
"**/*.{go,rs,java,kt,scala}",
|
|
666
660
|
"**/*.{c,cpp,cc,h,hpp}",
|
|
667
|
-
"**/*.{rb,php,swift}",
|
|
661
|
+
"**/*.{rb,php,inc,swift}",
|
|
668
662
|
"**/*.{vue,svelte,astro}",
|
|
669
663
|
"**/*.{sql,graphql,proto}",
|
|
670
664
|
"**/*.{yaml,yml,toml}",
|
|
@@ -781,9 +775,15 @@ function getDefaultSearchConfig() {
|
|
|
781
775
|
minScore: 0.1,
|
|
782
776
|
includeContext: true,
|
|
783
777
|
hybridWeight: 0.5,
|
|
778
|
+
fusionStrategy: "rrf",
|
|
779
|
+
rrfK: 60,
|
|
780
|
+
rerankTopN: 20,
|
|
784
781
|
contextLines: 0
|
|
785
782
|
};
|
|
786
783
|
}
|
|
784
|
+
function isValidFusionStrategy(value) {
|
|
785
|
+
return value === "weighted" || value === "rrf";
|
|
786
|
+
}
|
|
787
787
|
function getDefaultDebugConfig() {
|
|
788
788
|
return {
|
|
789
789
|
enabled: false,
|
|
@@ -838,6 +838,9 @@ function parseConfig(raw) {
|
|
|
838
838
|
minScore: typeof rawSearch.minScore === "number" ? rawSearch.minScore : defaultSearch.minScore,
|
|
839
839
|
includeContext: typeof rawSearch.includeContext === "boolean" ? rawSearch.includeContext : defaultSearch.includeContext,
|
|
840
840
|
hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
|
|
841
|
+
fusionStrategy: isValidFusionStrategy(rawSearch.fusionStrategy) ? rawSearch.fusionStrategy : defaultSearch.fusionStrategy,
|
|
842
|
+
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
843
|
+
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
841
844
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
|
|
842
845
|
};
|
|
843
846
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -2916,6 +2919,7 @@ function initializeLogger(config) {
|
|
|
2916
2919
|
// src/native/index.ts
|
|
2917
2920
|
import * as path3 from "path";
|
|
2918
2921
|
import * as os2 from "os";
|
|
2922
|
+
import * as module from "module";
|
|
2919
2923
|
import { fileURLToPath } from "url";
|
|
2920
2924
|
function getNativeBinding() {
|
|
2921
2925
|
const platform2 = os2.platform();
|
|
@@ -2935,17 +2939,22 @@ function getNativeBinding() {
|
|
|
2935
2939
|
throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
|
|
2936
2940
|
}
|
|
2937
2941
|
let currentDir;
|
|
2942
|
+
let requireTarget;
|
|
2938
2943
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
2939
2944
|
currentDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
2945
|
+
requireTarget = import.meta.url;
|
|
2940
2946
|
} else if (typeof __dirname !== "undefined") {
|
|
2941
2947
|
currentDir = __dirname;
|
|
2948
|
+
requireTarget = __filename;
|
|
2942
2949
|
} else {
|
|
2943
2950
|
currentDir = process.cwd();
|
|
2951
|
+
requireTarget = path3.join(currentDir, "index.js");
|
|
2944
2952
|
}
|
|
2945
2953
|
const isDevMode = currentDir.includes("/src/native");
|
|
2946
2954
|
const packageRoot = isDevMode ? path3.resolve(currentDir, "../..") : path3.resolve(currentDir, "..");
|
|
2947
2955
|
const nativePath = path3.join(packageRoot, "native", bindingName);
|
|
2948
|
-
|
|
2956
|
+
const require2 = module.createRequire(requireTarget);
|
|
2957
|
+
return require2(nativePath);
|
|
2949
2958
|
}
|
|
2950
2959
|
var native = getNativeBinding();
|
|
2951
2960
|
function parseFiles(files) {
|
|
@@ -2972,6 +2981,9 @@ function hashContent(content) {
|
|
|
2972
2981
|
function hashFile(filePath) {
|
|
2973
2982
|
return native.hashFile(filePath);
|
|
2974
2983
|
}
|
|
2984
|
+
function extractCalls(content, language) {
|
|
2985
|
+
return native.extractCalls(content, language);
|
|
2986
|
+
}
|
|
2975
2987
|
var VectorStore = class {
|
|
2976
2988
|
inner;
|
|
2977
2989
|
dimensions;
|
|
@@ -3303,6 +3315,12 @@ var Database = class {
|
|
|
3303
3315
|
getChunksByFile(filePath) {
|
|
3304
3316
|
return this.inner.getChunksByFile(filePath);
|
|
3305
3317
|
}
|
|
3318
|
+
getChunksByName(name) {
|
|
3319
|
+
return this.inner.getChunksByName(name);
|
|
3320
|
+
}
|
|
3321
|
+
getChunksByNameCi(name) {
|
|
3322
|
+
return this.inner.getChunksByNameCi(name);
|
|
3323
|
+
}
|
|
3306
3324
|
deleteChunksByFile(filePath) {
|
|
3307
3325
|
return this.inner.deleteChunksByFile(filePath);
|
|
3308
3326
|
}
|
|
@@ -3346,12 +3364,108 @@ var Database = class {
|
|
|
3346
3364
|
getStats() {
|
|
3347
3365
|
return this.inner.getStats();
|
|
3348
3366
|
}
|
|
3367
|
+
// ── Symbol methods ──────────────────────────────────────────────
|
|
3368
|
+
upsertSymbol(symbol) {
|
|
3369
|
+
this.inner.upsertSymbol(symbol);
|
|
3370
|
+
}
|
|
3371
|
+
upsertSymbolsBatch(symbols) {
|
|
3372
|
+
if (symbols.length === 0) return;
|
|
3373
|
+
this.inner.upsertSymbolsBatch(symbols);
|
|
3374
|
+
}
|
|
3375
|
+
getSymbolsByFile(filePath) {
|
|
3376
|
+
return this.inner.getSymbolsByFile(filePath);
|
|
3377
|
+
}
|
|
3378
|
+
getSymbolByName(name, filePath) {
|
|
3379
|
+
return this.inner.getSymbolByName(name, filePath) ?? null;
|
|
3380
|
+
}
|
|
3381
|
+
getSymbolsByName(name) {
|
|
3382
|
+
return this.inner.getSymbolsByName(name);
|
|
3383
|
+
}
|
|
3384
|
+
getSymbolsByNameCi(name) {
|
|
3385
|
+
return this.inner.getSymbolsByNameCi(name);
|
|
3386
|
+
}
|
|
3387
|
+
deleteSymbolsByFile(filePath) {
|
|
3388
|
+
return this.inner.deleteSymbolsByFile(filePath);
|
|
3389
|
+
}
|
|
3390
|
+
// ── Call Edge methods ────────────────────────────────────────────
|
|
3391
|
+
upsertCallEdge(edge) {
|
|
3392
|
+
this.inner.upsertCallEdge(edge);
|
|
3393
|
+
}
|
|
3394
|
+
upsertCallEdgesBatch(edges) {
|
|
3395
|
+
if (edges.length === 0) return;
|
|
3396
|
+
this.inner.upsertCallEdgesBatch(edges);
|
|
3397
|
+
}
|
|
3398
|
+
getCallers(targetName, branch) {
|
|
3399
|
+
return this.inner.getCallers(targetName, branch);
|
|
3400
|
+
}
|
|
3401
|
+
getCallersWithContext(targetName, branch) {
|
|
3402
|
+
return this.inner.getCallersWithContext(targetName, branch);
|
|
3403
|
+
}
|
|
3404
|
+
getCallees(symbolId, branch) {
|
|
3405
|
+
return this.inner.getCallees(symbolId, branch);
|
|
3406
|
+
}
|
|
3407
|
+
deleteCallEdgesByFile(filePath) {
|
|
3408
|
+
return this.inner.deleteCallEdgesByFile(filePath);
|
|
3409
|
+
}
|
|
3410
|
+
resolveCallEdge(edgeId, toSymbolId) {
|
|
3411
|
+
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
3412
|
+
}
|
|
3413
|
+
// ── Branch Symbol methods ────────────────────────────────────────
|
|
3414
|
+
addSymbolsToBranch(branch, symbolIds) {
|
|
3415
|
+
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
3416
|
+
}
|
|
3417
|
+
addSymbolsToBranchBatch(branch, symbolIds) {
|
|
3418
|
+
if (symbolIds.length === 0) return;
|
|
3419
|
+
this.inner.addSymbolsToBranchBatch(branch, symbolIds);
|
|
3420
|
+
}
|
|
3421
|
+
getBranchSymbolIds(branch) {
|
|
3422
|
+
return this.inner.getBranchSymbolIds(branch);
|
|
3423
|
+
}
|
|
3424
|
+
clearBranchSymbols(branch) {
|
|
3425
|
+
return this.inner.clearBranchSymbols(branch);
|
|
3426
|
+
}
|
|
3427
|
+
// ── GC methods for symbols/edges ─────────────────────────────────
|
|
3428
|
+
gcOrphanSymbols() {
|
|
3429
|
+
return this.inner.gcOrphanSymbols();
|
|
3430
|
+
}
|
|
3431
|
+
gcOrphanCallEdges() {
|
|
3432
|
+
return this.inner.gcOrphanCallEdges();
|
|
3433
|
+
}
|
|
3349
3434
|
};
|
|
3350
3435
|
|
|
3351
3436
|
// src/git/index.ts
|
|
3352
3437
|
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
3353
3438
|
import * as path4 from "path";
|
|
3354
|
-
|
|
3439
|
+
function readPackedRefs(gitDir) {
|
|
3440
|
+
const packedRefsPath = path4.join(gitDir, "packed-refs");
|
|
3441
|
+
if (!existsSync3(packedRefsPath)) {
|
|
3442
|
+
return [];
|
|
3443
|
+
}
|
|
3444
|
+
try {
|
|
3445
|
+
return readFileSync3(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
3446
|
+
} catch {
|
|
3447
|
+
return [];
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
function resolveCommonGitDir(gitDir) {
|
|
3451
|
+
const commonDirPath = path4.join(gitDir, "commondir");
|
|
3452
|
+
if (!existsSync3(commonDirPath)) {
|
|
3453
|
+
return gitDir;
|
|
3454
|
+
}
|
|
3455
|
+
try {
|
|
3456
|
+
const raw = readFileSync3(commonDirPath, "utf-8").trim();
|
|
3457
|
+
if (!raw) {
|
|
3458
|
+
return gitDir;
|
|
3459
|
+
}
|
|
3460
|
+
const resolved = path4.isAbsolute(raw) ? raw : path4.resolve(gitDir, raw);
|
|
3461
|
+
if (existsSync3(resolved)) {
|
|
3462
|
+
return resolved;
|
|
3463
|
+
}
|
|
3464
|
+
} catch {
|
|
3465
|
+
return gitDir;
|
|
3466
|
+
}
|
|
3467
|
+
return gitDir;
|
|
3468
|
+
}
|
|
3355
3469
|
function resolveGitDir(repoRoot) {
|
|
3356
3470
|
const gitPath = path4.join(repoRoot, ".git");
|
|
3357
3471
|
if (!existsSync3(gitPath)) {
|
|
@@ -3405,37 +3519,20 @@ function getCurrentBranch(repoRoot) {
|
|
|
3405
3519
|
}
|
|
3406
3520
|
function getBaseBranch(repoRoot) {
|
|
3407
3521
|
const gitDir = resolveGitDir(repoRoot);
|
|
3522
|
+
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
3408
3523
|
const candidates = ["main", "master", "develop", "trunk"];
|
|
3409
|
-
if (
|
|
3524
|
+
if (refStoreDir) {
|
|
3410
3525
|
for (const candidate of candidates) {
|
|
3411
|
-
const refPath = path4.join(
|
|
3526
|
+
const refPath = path4.join(refStoreDir, "refs", "heads", candidate);
|
|
3412
3527
|
if (existsSync3(refPath)) {
|
|
3413
3528
|
return candidate;
|
|
3414
3529
|
}
|
|
3415
|
-
const
|
|
3416
|
-
if (
|
|
3417
|
-
|
|
3418
|
-
const content = readFileSync3(packedRefsPath, "utf-8");
|
|
3419
|
-
if (content.includes(`refs/heads/${candidate}`)) {
|
|
3420
|
-
return candidate;
|
|
3421
|
-
}
|
|
3422
|
-
} catch {
|
|
3423
|
-
}
|
|
3530
|
+
const packedRefs = readPackedRefs(refStoreDir);
|
|
3531
|
+
if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
|
|
3532
|
+
return candidate;
|
|
3424
3533
|
}
|
|
3425
3534
|
}
|
|
3426
3535
|
}
|
|
3427
|
-
try {
|
|
3428
|
-
const result = execSync("git remote show origin", {
|
|
3429
|
-
cwd: repoRoot,
|
|
3430
|
-
encoding: "utf-8",
|
|
3431
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3432
|
-
});
|
|
3433
|
-
const match = result.match(/HEAD branch: (.+)/);
|
|
3434
|
-
if (match) {
|
|
3435
|
-
return match[1].trim();
|
|
3436
|
-
}
|
|
3437
|
-
} catch {
|
|
3438
|
-
}
|
|
3439
3536
|
return getCurrentBranch(repoRoot) ?? "main";
|
|
3440
3537
|
}
|
|
3441
3538
|
function getBranchOrDefault(repoRoot) {
|
|
@@ -3453,6 +3550,30 @@ function getHeadPath(repoRoot) {
|
|
|
3453
3550
|
}
|
|
3454
3551
|
|
|
3455
3552
|
// src/indexer/index.ts
|
|
3553
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
|
|
3554
|
+
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
3555
|
+
"function_declaration",
|
|
3556
|
+
"function",
|
|
3557
|
+
"arrow_function",
|
|
3558
|
+
"method_definition",
|
|
3559
|
+
"class_declaration",
|
|
3560
|
+
"interface_declaration",
|
|
3561
|
+
"type_alias_declaration",
|
|
3562
|
+
"enum_declaration",
|
|
3563
|
+
"function_definition",
|
|
3564
|
+
"class_definition",
|
|
3565
|
+
"decorated_definition",
|
|
3566
|
+
"method_declaration",
|
|
3567
|
+
"type_declaration",
|
|
3568
|
+
"type_spec",
|
|
3569
|
+
"function_item",
|
|
3570
|
+
"impl_item",
|
|
3571
|
+
"struct_item",
|
|
3572
|
+
"enum_item",
|
|
3573
|
+
"trait_item",
|
|
3574
|
+
"mod_item",
|
|
3575
|
+
"trait_declaration"
|
|
3576
|
+
]);
|
|
3456
3577
|
function float32ArrayToBuffer(arr) {
|
|
3457
3578
|
const float32 = new Float32Array(arr);
|
|
3458
3579
|
return Buffer.from(float32.buffer);
|
|
@@ -3477,6 +3598,891 @@ function isRateLimitError(error) {
|
|
|
3477
3598
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
3478
3599
|
}
|
|
3479
3600
|
var INDEX_METADATA_VERSION = "1";
|
|
3601
|
+
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
3602
|
+
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
3603
|
+
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
3604
|
+
var rankingPathTokenCache = /* @__PURE__ */ new Map();
|
|
3605
|
+
var rankingTextTokenCache = /* @__PURE__ */ new Map();
|
|
3606
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3607
|
+
"the",
|
|
3608
|
+
"and",
|
|
3609
|
+
"for",
|
|
3610
|
+
"with",
|
|
3611
|
+
"from",
|
|
3612
|
+
"that",
|
|
3613
|
+
"this",
|
|
3614
|
+
"into",
|
|
3615
|
+
"using",
|
|
3616
|
+
"where",
|
|
3617
|
+
"what",
|
|
3618
|
+
"when",
|
|
3619
|
+
"why",
|
|
3620
|
+
"how",
|
|
3621
|
+
"are",
|
|
3622
|
+
"was",
|
|
3623
|
+
"were",
|
|
3624
|
+
"be",
|
|
3625
|
+
"been",
|
|
3626
|
+
"being",
|
|
3627
|
+
"find",
|
|
3628
|
+
"show",
|
|
3629
|
+
"get",
|
|
3630
|
+
"run",
|
|
3631
|
+
"use",
|
|
3632
|
+
"code",
|
|
3633
|
+
"function",
|
|
3634
|
+
"implementation",
|
|
3635
|
+
"retrieve",
|
|
3636
|
+
"results",
|
|
3637
|
+
"result",
|
|
3638
|
+
"search",
|
|
3639
|
+
"pipeline",
|
|
3640
|
+
"top",
|
|
3641
|
+
"in",
|
|
3642
|
+
"on",
|
|
3643
|
+
"of",
|
|
3644
|
+
"to",
|
|
3645
|
+
"by",
|
|
3646
|
+
"as",
|
|
3647
|
+
"or",
|
|
3648
|
+
"an",
|
|
3649
|
+
"a"
|
|
3650
|
+
]);
|
|
3651
|
+
var TEST_PATH_SEGMENTS = [
|
|
3652
|
+
"tests/",
|
|
3653
|
+
"__tests__/",
|
|
3654
|
+
"/test/",
|
|
3655
|
+
"fixtures/",
|
|
3656
|
+
"benchmark",
|
|
3657
|
+
"README",
|
|
3658
|
+
"ARCHITECTURE",
|
|
3659
|
+
"docs/"
|
|
3660
|
+
];
|
|
3661
|
+
var IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS = [
|
|
3662
|
+
"tests/",
|
|
3663
|
+
"__tests__/",
|
|
3664
|
+
"/test/",
|
|
3665
|
+
"fixtures/",
|
|
3666
|
+
"benchmark",
|
|
3667
|
+
"readme",
|
|
3668
|
+
"architecture",
|
|
3669
|
+
"docs/",
|
|
3670
|
+
"examples/",
|
|
3671
|
+
"example/",
|
|
3672
|
+
".github/",
|
|
3673
|
+
"/scripts/",
|
|
3674
|
+
"/migrations/",
|
|
3675
|
+
"/generated/"
|
|
3676
|
+
];
|
|
3677
|
+
var SOURCE_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3678
|
+
"implement",
|
|
3679
|
+
"implementation",
|
|
3680
|
+
"function",
|
|
3681
|
+
"method",
|
|
3682
|
+
"class",
|
|
3683
|
+
"logic",
|
|
3684
|
+
"algorithm",
|
|
3685
|
+
"pipeline",
|
|
3686
|
+
"indexer",
|
|
3687
|
+
"where"
|
|
3688
|
+
]);
|
|
3689
|
+
var DOC_TEST_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3690
|
+
"test",
|
|
3691
|
+
"tests",
|
|
3692
|
+
"fixture",
|
|
3693
|
+
"fixtures",
|
|
3694
|
+
"benchmark",
|
|
3695
|
+
"readme",
|
|
3696
|
+
"docs",
|
|
3697
|
+
"documentation"
|
|
3698
|
+
]);
|
|
3699
|
+
var DOC_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3700
|
+
"readme",
|
|
3701
|
+
"docs",
|
|
3702
|
+
"documentation",
|
|
3703
|
+
"guide",
|
|
3704
|
+
"usage"
|
|
3705
|
+
]);
|
|
3706
|
+
function setBoundedCache(cache, key, value) {
|
|
3707
|
+
if (cache.size >= RANKING_TOKEN_CACHE_LIMIT) {
|
|
3708
|
+
const oldest = cache.keys().next().value;
|
|
3709
|
+
if (oldest !== void 0) {
|
|
3710
|
+
cache.delete(oldest);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
cache.set(key, value);
|
|
3714
|
+
}
|
|
3715
|
+
function tokenizeTextForRanking(text) {
|
|
3716
|
+
if (!text) {
|
|
3717
|
+
return /* @__PURE__ */ new Set();
|
|
3718
|
+
}
|
|
3719
|
+
const lowered = text.toLowerCase();
|
|
3720
|
+
const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
|
|
3721
|
+
if (cache) {
|
|
3722
|
+
return cache;
|
|
3723
|
+
}
|
|
3724
|
+
const tokens = new Set(
|
|
3725
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !STOPWORDS.has(token))
|
|
3726
|
+
);
|
|
3727
|
+
setBoundedCache(rankingQueryTokenCache, lowered, tokens);
|
|
3728
|
+
setBoundedCache(rankingTextTokenCache, lowered, tokens);
|
|
3729
|
+
return tokens;
|
|
3730
|
+
}
|
|
3731
|
+
function splitPathTokens(filePath) {
|
|
3732
|
+
const lowered = filePath.toLowerCase();
|
|
3733
|
+
const cache = rankingPathTokenCache.get(lowered);
|
|
3734
|
+
if (cache) {
|
|
3735
|
+
return cache;
|
|
3736
|
+
}
|
|
3737
|
+
const normalized = lowered.replace(/[^a-z0-9/._-]/g, " ").split(/[/._-]+/).filter((token) => token.length > 1);
|
|
3738
|
+
const tokens = new Set(normalized);
|
|
3739
|
+
setBoundedCache(rankingPathTokenCache, lowered, tokens);
|
|
3740
|
+
return tokens;
|
|
3741
|
+
}
|
|
3742
|
+
function splitNameTokens(name) {
|
|
3743
|
+
if (!name) {
|
|
3744
|
+
return /* @__PURE__ */ new Set();
|
|
3745
|
+
}
|
|
3746
|
+
const lowered = name.toLowerCase();
|
|
3747
|
+
const cache = rankingNameTokenCache.get(lowered);
|
|
3748
|
+
if (cache) {
|
|
3749
|
+
return cache;
|
|
3750
|
+
}
|
|
3751
|
+
const tokens = new Set(
|
|
3752
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1)
|
|
3753
|
+
);
|
|
3754
|
+
setBoundedCache(rankingNameTokenCache, lowered, tokens);
|
|
3755
|
+
return tokens;
|
|
3756
|
+
}
|
|
3757
|
+
function chunkTypeBoost(chunkType) {
|
|
3758
|
+
switch (chunkType) {
|
|
3759
|
+
case "function":
|
|
3760
|
+
case "function_declaration":
|
|
3761
|
+
case "method":
|
|
3762
|
+
case "method_definition":
|
|
3763
|
+
case "class":
|
|
3764
|
+
case "class_declaration":
|
|
3765
|
+
return 0.2;
|
|
3766
|
+
case "interface":
|
|
3767
|
+
case "type":
|
|
3768
|
+
case "enum":
|
|
3769
|
+
case "struct":
|
|
3770
|
+
case "impl":
|
|
3771
|
+
case "trait":
|
|
3772
|
+
case "module":
|
|
3773
|
+
return 0.1;
|
|
3774
|
+
default:
|
|
3775
|
+
return 0;
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
function isTestOrDocPath(filePath) {
|
|
3779
|
+
return TEST_PATH_SEGMENTS.some((segment) => filePath.includes(segment));
|
|
3780
|
+
}
|
|
3781
|
+
function isLikelyImplementationPath(filePath) {
|
|
3782
|
+
const lowered = filePath.toLowerCase();
|
|
3783
|
+
if (IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS.some((segment) => lowered.includes(segment))) {
|
|
3784
|
+
return false;
|
|
3785
|
+
}
|
|
3786
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
3787
|
+
if (["md", "mdx", "txt", "rst", "adoc", "snap", "json", "yaml", "yml", "lock"].includes(ext)) {
|
|
3788
|
+
return false;
|
|
3789
|
+
}
|
|
3790
|
+
return true;
|
|
3791
|
+
}
|
|
3792
|
+
function classifyQueryIntent(tokens) {
|
|
3793
|
+
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
3794
|
+
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
3795
|
+
return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
|
|
3796
|
+
}
|
|
3797
|
+
function classifyQueryIntentRaw(query) {
|
|
3798
|
+
const lowerQuery = query.toLowerCase();
|
|
3799
|
+
const docTestRawHits = Array.from(DOC_TEST_INTENT_HINTS).filter(
|
|
3800
|
+
(hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)
|
|
3801
|
+
).length;
|
|
3802
|
+
const sourceRawHits = [
|
|
3803
|
+
"implement",
|
|
3804
|
+
"implementation",
|
|
3805
|
+
"implements",
|
|
3806
|
+
"function",
|
|
3807
|
+
"method",
|
|
3808
|
+
"class",
|
|
3809
|
+
"logic",
|
|
3810
|
+
"algorithm",
|
|
3811
|
+
"pipeline",
|
|
3812
|
+
"indexer"
|
|
3813
|
+
].filter((hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)).length;
|
|
3814
|
+
if (docTestRawHits > sourceRawHits) {
|
|
3815
|
+
return "doc_test";
|
|
3816
|
+
}
|
|
3817
|
+
if (sourceRawHits > docTestRawHits) {
|
|
3818
|
+
return "source";
|
|
3819
|
+
}
|
|
3820
|
+
const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
|
|
3821
|
+
const hasIdentifierHints = extractIdentifierHints(query).length > 0;
|
|
3822
|
+
if (hasWhereIsPattern && hasIdentifierHints && docTestRawHits === 0) {
|
|
3823
|
+
return "source";
|
|
3824
|
+
}
|
|
3825
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
3826
|
+
return classifyQueryIntent(queryTokens);
|
|
3827
|
+
}
|
|
3828
|
+
function classifyDocIntent(tokens) {
|
|
3829
|
+
const docHits = tokens.filter((t) => DOC_INTENT_HINTS.has(t)).length;
|
|
3830
|
+
const testHits = tokens.filter((t) => ["test", "tests", "fixture", "fixtures", "benchmark"].includes(t)).length;
|
|
3831
|
+
if (docHits > 0 && testHits === 0) return "docs";
|
|
3832
|
+
if (testHits > 0 && docHits === 0) return "test";
|
|
3833
|
+
if (testHits > 0 || docHits > 0) return "mixed";
|
|
3834
|
+
return "none";
|
|
3835
|
+
}
|
|
3836
|
+
function isImplementationChunkType(chunkType) {
|
|
3837
|
+
return [
|
|
3838
|
+
"export_statement",
|
|
3839
|
+
"function",
|
|
3840
|
+
"function_declaration",
|
|
3841
|
+
"method",
|
|
3842
|
+
"method_definition",
|
|
3843
|
+
"class",
|
|
3844
|
+
"class_declaration",
|
|
3845
|
+
"interface",
|
|
3846
|
+
"type",
|
|
3847
|
+
"enum",
|
|
3848
|
+
"module"
|
|
3849
|
+
].includes(chunkType);
|
|
3850
|
+
}
|
|
3851
|
+
function extractIdentifierHints(query) {
|
|
3852
|
+
const identifiers = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3853
|
+
return identifiers.filter((id) => id.length >= 3).filter((id) => {
|
|
3854
|
+
const lower = id.toLowerCase();
|
|
3855
|
+
if (STOPWORDS.has(lower)) return false;
|
|
3856
|
+
return /[A-Z]/.test(id) || id.includes("_") || id.endsWith("Results") || id.endsWith("Result");
|
|
3857
|
+
}).map((id) => id.toLowerCase());
|
|
3858
|
+
}
|
|
3859
|
+
function extractCodeTermHints(query) {
|
|
3860
|
+
const terms = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3861
|
+
return terms.map((term) => term.toLowerCase()).filter((term) => term.length >= 3).filter((term) => !STOPWORDS.has(term));
|
|
3862
|
+
}
|
|
3863
|
+
function normalizeIdentifierVariants(identifier) {
|
|
3864
|
+
const lower = identifier.toLowerCase();
|
|
3865
|
+
const compact = lower.replace(/[^a-z0-9]/g, "");
|
|
3866
|
+
const snake = compact.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
3867
|
+
const kebab = snake.replace(/_/g, "-");
|
|
3868
|
+
const variants = [lower, compact, snake, kebab].filter((value) => value.length > 0);
|
|
3869
|
+
return Array.from(new Set(variants));
|
|
3870
|
+
}
|
|
3871
|
+
function scoreIdentifierMatch(name, filePath, hints) {
|
|
3872
|
+
const nameLower = (name ?? "").toLowerCase();
|
|
3873
|
+
const pathLower = filePath.toLowerCase();
|
|
3874
|
+
let best = 0;
|
|
3875
|
+
for (const hint of hints) {
|
|
3876
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3877
|
+
for (const variant of variants) {
|
|
3878
|
+
if (nameLower === variant) {
|
|
3879
|
+
best = Math.max(best, 1);
|
|
3880
|
+
} else if (nameLower.includes(variant)) {
|
|
3881
|
+
best = Math.max(best, 0.8);
|
|
3882
|
+
} else if (pathLower.includes(variant)) {
|
|
3883
|
+
best = Math.max(best, 0.6);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
return best;
|
|
3888
|
+
}
|
|
3889
|
+
function extractPrimaryIdentifierQueryHint(query) {
|
|
3890
|
+
const identifiers = extractIdentifierHints(query);
|
|
3891
|
+
if (identifiers.length > 0) {
|
|
3892
|
+
return identifiers[0] ?? null;
|
|
3893
|
+
}
|
|
3894
|
+
const codeTerms = extractCodeTermHints(query);
|
|
3895
|
+
const best = codeTerms.find((term) => term.length >= 6);
|
|
3896
|
+
return best ?? null;
|
|
3897
|
+
}
|
|
3898
|
+
var FILE_PATH_HINT_EXTENSIONS = [
|
|
3899
|
+
"ts",
|
|
3900
|
+
"tsx",
|
|
3901
|
+
"js",
|
|
3902
|
+
"jsx",
|
|
3903
|
+
"mjs",
|
|
3904
|
+
"cjs",
|
|
3905
|
+
"mts",
|
|
3906
|
+
"cts",
|
|
3907
|
+
"py",
|
|
3908
|
+
"rs",
|
|
3909
|
+
"go",
|
|
3910
|
+
"java",
|
|
3911
|
+
"kt",
|
|
3912
|
+
"kts",
|
|
3913
|
+
"swift",
|
|
3914
|
+
"rb",
|
|
3915
|
+
"php",
|
|
3916
|
+
"c",
|
|
3917
|
+
"h",
|
|
3918
|
+
"cc",
|
|
3919
|
+
"cpp",
|
|
3920
|
+
"cxx",
|
|
3921
|
+
"hpp",
|
|
3922
|
+
"cs",
|
|
3923
|
+
"scala",
|
|
3924
|
+
"lua",
|
|
3925
|
+
"sh",
|
|
3926
|
+
"bash",
|
|
3927
|
+
"zsh",
|
|
3928
|
+
"json",
|
|
3929
|
+
"yaml",
|
|
3930
|
+
"yml",
|
|
3931
|
+
"toml"
|
|
3932
|
+
];
|
|
3933
|
+
var FILE_PATH_HINT_SUFFIX_REGEX = new RegExp(
|
|
3934
|
+
"\\s+\\bin\\s+[\"'`]?((?:\\.\\/)?(?:[A-Za-z0-9._-]+\\/)+[A-Za-z0-9._-]+\\.(?:" + FILE_PATH_HINT_EXTENSIONS.join("|") + "))[\"'`]?[\\])}>.,;!?]*\\s*$",
|
|
3935
|
+
"i"
|
|
3936
|
+
);
|
|
3937
|
+
function normalizeFilePathForHintMatch(filePath) {
|
|
3938
|
+
return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
3939
|
+
}
|
|
3940
|
+
function pathMatchesHint(filePath, hint) {
|
|
3941
|
+
const normalizedPath = normalizeFilePathForHintMatch(filePath);
|
|
3942
|
+
const normalizedHint = normalizeFilePathForHintMatch(hint);
|
|
3943
|
+
return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
|
|
3944
|
+
}
|
|
3945
|
+
function extractFilePathHint(query) {
|
|
3946
|
+
const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
|
|
3947
|
+
const rawPath = match?.[1];
|
|
3948
|
+
if (!rawPath) {
|
|
3949
|
+
return null;
|
|
3950
|
+
}
|
|
3951
|
+
return rawPath.replace(/^\.\//, "");
|
|
3952
|
+
}
|
|
3953
|
+
function stripFilePathHint(query) {
|
|
3954
|
+
const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
|
|
3955
|
+
return stripped.length > 0 ? stripped : query;
|
|
3956
|
+
}
|
|
3957
|
+
function buildDeterministicIdentifierPass(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
3958
|
+
if (!prioritizeSourcePaths) {
|
|
3959
|
+
return [];
|
|
3960
|
+
}
|
|
3961
|
+
const primary = extractPrimaryIdentifierQueryHint(query);
|
|
3962
|
+
if (!primary) {
|
|
3963
|
+
return [];
|
|
3964
|
+
}
|
|
3965
|
+
const filePathHint = extractFilePathHint(query);
|
|
3966
|
+
const primaryVariants = normalizeIdentifierVariants(primary);
|
|
3967
|
+
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);
|
|
3968
|
+
const deterministic = candidates.filter(
|
|
3969
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
3970
|
+
).map((candidate) => {
|
|
3971
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
3972
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
3973
|
+
let maxMatch = 0;
|
|
3974
|
+
const nameMatchesPrimary = primaryVariants.some(
|
|
3975
|
+
(variant) => nameLower === variant || nameLower.replace(/[^a-z0-9]/g, "") === variant.replace(/[^a-z0-9]/g, "")
|
|
3976
|
+
);
|
|
3977
|
+
const pathMatchesFileHint = filePathHint ? pathMatchesHint(candidate.metadata.filePath, filePathHint) : false;
|
|
3978
|
+
for (const hint of hints) {
|
|
3979
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
3980
|
+
for (const variant of variants) {
|
|
3981
|
+
if (nameLower === variant) {
|
|
3982
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
3983
|
+
} else if (nameLower.includes(variant)) {
|
|
3984
|
+
maxMatch = Math.max(maxMatch, 0.85);
|
|
3985
|
+
} else if (pathLower.includes(variant)) {
|
|
3986
|
+
maxMatch = Math.max(maxMatch, 0.7);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
if (pathMatchesFileHint && nameMatchesPrimary) {
|
|
3991
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
candidate,
|
|
3995
|
+
maxMatch,
|
|
3996
|
+
pathMatchesFileHint,
|
|
3997
|
+
nameMatchesPrimary
|
|
3998
|
+
};
|
|
3999
|
+
}).filter((entry) => entry.maxMatch >= 0.7).sort((a, b) => {
|
|
4000
|
+
const aAnchored = a.pathMatchesFileHint && a.nameMatchesPrimary ? 1 : 0;
|
|
4001
|
+
const bAnchored = b.pathMatchesFileHint && b.nameMatchesPrimary ? 1 : 0;
|
|
4002
|
+
if (aAnchored !== bAnchored) return bAnchored - aAnchored;
|
|
4003
|
+
if (b.maxMatch !== a.maxMatch) return b.maxMatch - a.maxMatch;
|
|
4004
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4005
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4006
|
+
}).slice(0, Math.max(limit * 2, 12));
|
|
4007
|
+
return deterministic.map((entry) => ({
|
|
4008
|
+
id: entry.candidate.id,
|
|
4009
|
+
score: entry.pathMatchesFileHint && entry.nameMatchesPrimary ? 0.995 : Math.min(1, 0.9 + entry.maxMatch * 0.09),
|
|
4010
|
+
metadata: entry.candidate.metadata
|
|
4011
|
+
}));
|
|
4012
|
+
}
|
|
4013
|
+
function fuseResultsWeighted(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4014
|
+
const semanticWeight = 1 - keywordWeight;
|
|
4015
|
+
const fusedScores = /* @__PURE__ */ new Map();
|
|
4016
|
+
for (const r of semanticResults) {
|
|
4017
|
+
fusedScores.set(r.id, {
|
|
4018
|
+
score: r.score * semanticWeight,
|
|
4019
|
+
metadata: r.metadata
|
|
4020
|
+
});
|
|
4021
|
+
}
|
|
4022
|
+
for (const r of keywordResults) {
|
|
4023
|
+
const existing = fusedScores.get(r.id);
|
|
4024
|
+
if (existing) {
|
|
4025
|
+
existing.score += r.score * keywordWeight;
|
|
4026
|
+
} else {
|
|
4027
|
+
fusedScores.set(r.id, {
|
|
4028
|
+
score: r.score * keywordWeight,
|
|
4029
|
+
metadata: r.metadata
|
|
4030
|
+
});
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4034
|
+
id,
|
|
4035
|
+
score: data.score,
|
|
4036
|
+
metadata: data.metadata
|
|
4037
|
+
}));
|
|
4038
|
+
results.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4039
|
+
return results.slice(0, limit);
|
|
4040
|
+
}
|
|
4041
|
+
function fuseResultsRrf(semanticResults, keywordResults, rrfK, limit) {
|
|
4042
|
+
const maxPossibleRaw = 2 / (rrfK + 1);
|
|
4043
|
+
const rankByIdSemantic = /* @__PURE__ */ new Map();
|
|
4044
|
+
const rankByIdKeyword = /* @__PURE__ */ new Map();
|
|
4045
|
+
const metadataById = /* @__PURE__ */ new Map();
|
|
4046
|
+
semanticResults.forEach((result, index) => {
|
|
4047
|
+
rankByIdSemantic.set(result.id, index + 1);
|
|
4048
|
+
metadataById.set(result.id, result.metadata);
|
|
4049
|
+
});
|
|
4050
|
+
keywordResults.forEach((result, index) => {
|
|
4051
|
+
rankByIdKeyword.set(result.id, index + 1);
|
|
4052
|
+
if (!metadataById.has(result.id)) {
|
|
4053
|
+
metadataById.set(result.id, result.metadata);
|
|
4054
|
+
}
|
|
4055
|
+
});
|
|
4056
|
+
const allIds = /* @__PURE__ */ new Set([...rankByIdSemantic.keys(), ...rankByIdKeyword.keys()]);
|
|
4057
|
+
const fused = [];
|
|
4058
|
+
for (const id of allIds) {
|
|
4059
|
+
const semanticRank = rankByIdSemantic.get(id);
|
|
4060
|
+
const keywordRank = rankByIdKeyword.get(id);
|
|
4061
|
+
const semanticScore = semanticRank ? 1 / (rrfK + semanticRank) : 0;
|
|
4062
|
+
const keywordScore = keywordRank ? 1 / (rrfK + keywordRank) : 0;
|
|
4063
|
+
const metadata = metadataById.get(id);
|
|
4064
|
+
if (!metadata) continue;
|
|
4065
|
+
fused.push({
|
|
4066
|
+
id,
|
|
4067
|
+
score: maxPossibleRaw > 0 ? (semanticScore + keywordScore) / maxPossibleRaw : 0,
|
|
4068
|
+
metadata
|
|
4069
|
+
});
|
|
4070
|
+
}
|
|
4071
|
+
fused.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4072
|
+
return fused.slice(0, limit);
|
|
4073
|
+
}
|
|
4074
|
+
function rerankResults(query, candidates, rerankTopN, options) {
|
|
4075
|
+
if (rerankTopN <= 0 || candidates.length <= 1) {
|
|
4076
|
+
return candidates;
|
|
4077
|
+
}
|
|
4078
|
+
const topN = Math.min(rerankTopN, candidates.length);
|
|
4079
|
+
const queryTokens = tokenizeTextForRanking(query);
|
|
4080
|
+
if (queryTokens.size === 0) {
|
|
4081
|
+
return candidates;
|
|
4082
|
+
}
|
|
4083
|
+
const queryTokenList = Array.from(queryTokens);
|
|
4084
|
+
const intent = classifyQueryIntentRaw(query);
|
|
4085
|
+
const docIntent = classifyDocIntent(queryTokenList);
|
|
4086
|
+
const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
|
|
4087
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4088
|
+
const head = candidates.slice(0, topN).map((candidate, idx) => {
|
|
4089
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4090
|
+
const nameTokens = splitNameTokens(candidate.metadata.name ?? "");
|
|
4091
|
+
const chunkTypeTokens = tokenizeTextForRanking(candidate.metadata.chunkType);
|
|
4092
|
+
let exactOrPrefixNameHits = 0;
|
|
4093
|
+
let pathOverlap = 0;
|
|
4094
|
+
let chunkTypeHits = 0;
|
|
4095
|
+
for (const token of queryTokenList) {
|
|
4096
|
+
if (nameTokens.has(token)) {
|
|
4097
|
+
exactOrPrefixNameHits += 1;
|
|
4098
|
+
} else {
|
|
4099
|
+
for (const nameToken of nameTokens) {
|
|
4100
|
+
if (nameToken.startsWith(token) || token.startsWith(nameToken)) {
|
|
4101
|
+
exactOrPrefixNameHits += 1;
|
|
4102
|
+
break;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
if (pathTokens.has(token)) {
|
|
4107
|
+
pathOverlap += 1;
|
|
4108
|
+
}
|
|
4109
|
+
if (chunkTypeTokens.has(token)) {
|
|
4110
|
+
chunkTypeHits += 1;
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
const likelyTestOrDoc = isTestOrDocPath(candidate.metadata.filePath);
|
|
4114
|
+
const lowerPath = candidate.metadata.filePath.toLowerCase();
|
|
4115
|
+
const lowerName = (candidate.metadata.name ?? "").toLowerCase();
|
|
4116
|
+
const hasIdentifierMatch = identifierHints.some((id) => lowerPath.includes(id) || lowerName.includes(id));
|
|
4117
|
+
const implementationPathBoost = preferSourcePaths && isLikelyImplementationPath(candidate.metadata.filePath) ? 0.08 : 0;
|
|
4118
|
+
const isReadmePath = candidate.metadata.filePath.toLowerCase().includes("readme");
|
|
4119
|
+
const testDocPenalty = preferSourcePaths && likelyTestOrDoc ? 0.12 : 0;
|
|
4120
|
+
const readmeDocBoost = !preferSourcePaths && isReadmePath ? 0.08 : 0;
|
|
4121
|
+
const identifierBoost = hasIdentifierMatch ? 0.12 : 0;
|
|
4122
|
+
const tokenCoverage = queryTokenList.length > 0 ? (exactOrPrefixNameHits + pathOverlap + chunkTypeHits) / queryTokenList.length : 0;
|
|
4123
|
+
const coverageBoost = Math.min(0.12, tokenCoverage * 0.06);
|
|
4124
|
+
const deterministicBoost = exactOrPrefixNameHits * 0.08 + pathOverlap * 0.03 + chunkTypeHits * 0.02 + coverageBoost + identifierBoost + implementationPathBoost - testDocPenalty + readmeDocBoost + chunkTypeBoost(candidate.metadata.chunkType);
|
|
4125
|
+
return {
|
|
4126
|
+
candidate,
|
|
4127
|
+
boostedScore: candidate.score + deterministicBoost,
|
|
4128
|
+
originalIndex: idx,
|
|
4129
|
+
hasIdentifierMatch,
|
|
4130
|
+
implementationChunk: isImplementationChunkType(candidate.metadata.chunkType),
|
|
4131
|
+
isLikelyImplementationPath: isLikelyImplementationPath(candidate.metadata.filePath),
|
|
4132
|
+
isTestOrDocPath: likelyTestOrDoc,
|
|
4133
|
+
isReadmePath
|
|
4134
|
+
};
|
|
4135
|
+
});
|
|
4136
|
+
head.sort((a, b) => {
|
|
4137
|
+
if (b.boostedScore !== a.boostedScore) return b.boostedScore - a.boostedScore;
|
|
4138
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4139
|
+
if (a.originalIndex !== b.originalIndex) return a.originalIndex - b.originalIndex;
|
|
4140
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4141
|
+
});
|
|
4142
|
+
if (preferSourcePaths) {
|
|
4143
|
+
head.sort((a, b) => {
|
|
4144
|
+
const aId = a.hasIdentifierMatch ? 1 : 0;
|
|
4145
|
+
const bId = b.hasIdentifierMatch ? 1 : 0;
|
|
4146
|
+
if (aId !== bId) return bId - aId;
|
|
4147
|
+
const aImpl = a.implementationChunk ? 1 : 0;
|
|
4148
|
+
const bImpl = b.implementationChunk ? 1 : 0;
|
|
4149
|
+
if (aImpl !== bImpl) return bImpl - aImpl;
|
|
4150
|
+
const aImplementationPath = a.isLikelyImplementationPath ? 1 : 0;
|
|
4151
|
+
const bImplementationPath = b.isLikelyImplementationPath ? 1 : 0;
|
|
4152
|
+
if (aImplementationPath !== bImplementationPath) return bImplementationPath - aImplementationPath;
|
|
4153
|
+
const aTestDoc = a.isTestOrDocPath ? 1 : 0;
|
|
4154
|
+
const bTestDoc = b.isTestOrDocPath ? 1 : 0;
|
|
4155
|
+
if (aTestDoc !== bTestDoc) return aTestDoc - bTestDoc;
|
|
4156
|
+
return 0;
|
|
4157
|
+
});
|
|
4158
|
+
} else if (docIntent === "docs") {
|
|
4159
|
+
head.sort((a, b) => {
|
|
4160
|
+
const aReadme = a.isReadmePath ? 1 : 0;
|
|
4161
|
+
const bReadme = b.isReadmePath ? 1 : 0;
|
|
4162
|
+
if (aReadme !== bReadme) return bReadme - aReadme;
|
|
4163
|
+
return 0;
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4166
|
+
const tail = candidates.slice(topN);
|
|
4167
|
+
return [...head.map((entry) => entry.candidate), ...tail];
|
|
4168
|
+
}
|
|
4169
|
+
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4170
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4171
|
+
const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
|
|
4172
|
+
const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
|
|
4173
|
+
const rerankPool = fused.slice(0, rerankPoolLimit);
|
|
4174
|
+
return rerankResults(query, rerankPool, options.rerankTopN, {
|
|
4175
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
4179
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4180
|
+
const bounded = semanticResults.slice(0, overfetchLimit);
|
|
4181
|
+
return rerankResults(query, bounded, options.rerankTopN, {
|
|
4182
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4186
|
+
if (combined.length === 0) {
|
|
4187
|
+
return combined;
|
|
4188
|
+
}
|
|
4189
|
+
if (!prioritizeSourcePaths) {
|
|
4190
|
+
return combined;
|
|
4191
|
+
}
|
|
4192
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4193
|
+
if (identifierHints.length === 0) {
|
|
4194
|
+
return combined;
|
|
4195
|
+
}
|
|
4196
|
+
const combinedById = new Map(combined.map((candidate) => [candidate.id, candidate]));
|
|
4197
|
+
const candidateUnion = /* @__PURE__ */ new Map();
|
|
4198
|
+
for (const candidate of semanticCandidates) {
|
|
4199
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4200
|
+
}
|
|
4201
|
+
for (const candidate of keywordCandidates) {
|
|
4202
|
+
if (!candidateUnion.has(candidate.id)) {
|
|
4203
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
if (database) {
|
|
4207
|
+
for (const identifier of identifierHints) {
|
|
4208
|
+
const symbols = database.getSymbolsByName(identifier);
|
|
4209
|
+
for (const symbol of symbols) {
|
|
4210
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4211
|
+
for (const chunk of chunks) {
|
|
4212
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4213
|
+
continue;
|
|
4214
|
+
}
|
|
4215
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4216
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4217
|
+
continue;
|
|
4218
|
+
}
|
|
4219
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
const existing = combinedById.get(chunk.chunkId) ?? candidateUnion.get(chunk.chunkId);
|
|
4226
|
+
const metadata = existing?.metadata ?? {
|
|
4227
|
+
filePath: chunk.filePath,
|
|
4228
|
+
startLine: chunk.startLine,
|
|
4229
|
+
endLine: chunk.endLine,
|
|
4230
|
+
chunkType,
|
|
4231
|
+
name: chunk.name ?? void 0,
|
|
4232
|
+
language: chunk.language,
|
|
4233
|
+
hash: chunk.contentHash
|
|
4234
|
+
};
|
|
4235
|
+
const baselineScore = existing?.score ?? 0.5;
|
|
4236
|
+
candidateUnion.set(chunk.chunkId, {
|
|
4237
|
+
id: chunk.chunkId,
|
|
4238
|
+
score: Math.min(1, baselineScore + 0.5),
|
|
4239
|
+
metadata
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
const promoted = [];
|
|
4246
|
+
for (const candidate of candidateUnion.values()) {
|
|
4247
|
+
const filePathLower = candidate.metadata.filePath.toLowerCase();
|
|
4248
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4249
|
+
const exactIdentifierMatch = identifierHints.some((hint) => nameLower === hint);
|
|
4250
|
+
const hasIdentifierMatch = exactIdentifierMatch || identifierHints.some(
|
|
4251
|
+
(hint) => nameLower.includes(hint) || filePathLower.includes(hint)
|
|
4252
|
+
);
|
|
4253
|
+
if (!hasIdentifierMatch) {
|
|
4254
|
+
continue;
|
|
4255
|
+
}
|
|
4256
|
+
if (!isImplementationChunkType(candidate.metadata.chunkType)) {
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
if (!isLikelyImplementationPath(candidate.metadata.filePath)) {
|
|
4260
|
+
continue;
|
|
4261
|
+
}
|
|
4262
|
+
const existing = combinedById.get(candidate.id) ?? candidate;
|
|
4263
|
+
const rescueBoost = exactIdentifierMatch ? 0.45 : 0.25;
|
|
4264
|
+
const boostedScore = Math.min(1, Math.max(existing.score, candidate.score) + rescueBoost);
|
|
4265
|
+
promoted.push({
|
|
4266
|
+
id: existing.id,
|
|
4267
|
+
score: boostedScore,
|
|
4268
|
+
metadata: existing.metadata
|
|
4269
|
+
});
|
|
4270
|
+
}
|
|
4271
|
+
if (promoted.length === 0) {
|
|
4272
|
+
return combined;
|
|
4273
|
+
}
|
|
4274
|
+
promoted.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4275
|
+
const promotedIds = new Set(promoted.map((candidate) => candidate.id));
|
|
4276
|
+
const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
|
|
4277
|
+
return [...promoted, ...remainder];
|
|
4278
|
+
}
|
|
4279
|
+
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4280
|
+
if (!prioritizeSourcePaths) {
|
|
4281
|
+
return [];
|
|
4282
|
+
}
|
|
4283
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4284
|
+
const codeTermHints = extractCodeTermHints(query);
|
|
4285
|
+
if (identifierHints.length === 0 && codeTermHints.length === 0) {
|
|
4286
|
+
return [];
|
|
4287
|
+
}
|
|
4288
|
+
const symbolCandidates = /* @__PURE__ */ new Map();
|
|
4289
|
+
const filePathHint = extractFilePathHint(query);
|
|
4290
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4291
|
+
const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
|
|
4292
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4293
|
+
return;
|
|
4294
|
+
}
|
|
4295
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4296
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
const nameLower = (chunk.name ?? "").toLowerCase();
|
|
4303
|
+
const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
|
|
4304
|
+
const base = baseScore ?? (exactName ? 0.99 : 0.88);
|
|
4305
|
+
const existing = symbolCandidates.get(chunk.chunkId);
|
|
4306
|
+
if (!existing || base > existing.score) {
|
|
4307
|
+
symbolCandidates.set(chunk.chunkId, {
|
|
4308
|
+
id: chunk.chunkId,
|
|
4309
|
+
score: base,
|
|
4310
|
+
metadata: {
|
|
4311
|
+
filePath: chunk.filePath,
|
|
4312
|
+
startLine: chunk.startLine,
|
|
4313
|
+
endLine: chunk.endLine,
|
|
4314
|
+
chunkType,
|
|
4315
|
+
name: chunk.name ?? void 0,
|
|
4316
|
+
language: chunk.language,
|
|
4317
|
+
hash: chunk.contentHash
|
|
4318
|
+
}
|
|
4319
|
+
});
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
const normalizedHints = identifierHints.flatMap((hint) => [
|
|
4323
|
+
hint,
|
|
4324
|
+
hint.replace(/_/g, ""),
|
|
4325
|
+
hint.replace(/_/g, "-")
|
|
4326
|
+
]).filter((hint, idx, arr) => hint.length >= 3 && arr.indexOf(hint) === idx).slice(0, 6);
|
|
4327
|
+
for (const identifier of normalizedHints) {
|
|
4328
|
+
const symbols = [
|
|
4329
|
+
...database.getSymbolsByName(identifier),
|
|
4330
|
+
...database.getSymbolsByNameCi(identifier)
|
|
4331
|
+
];
|
|
4332
|
+
const chunksByName = [
|
|
4333
|
+
...database.getChunksByName(identifier),
|
|
4334
|
+
...database.getChunksByNameCi(identifier)
|
|
4335
|
+
];
|
|
4336
|
+
const normalizedIdentifier = identifier.replace(/_/g, "");
|
|
4337
|
+
const dedupSymbols = /* @__PURE__ */ new Map();
|
|
4338
|
+
for (const symbol of symbols) {
|
|
4339
|
+
dedupSymbols.set(symbol.id, symbol);
|
|
4340
|
+
}
|
|
4341
|
+
for (const symbol of dedupSymbols.values()) {
|
|
4342
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4343
|
+
for (const chunk of chunks) {
|
|
4344
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4345
|
+
continue;
|
|
4346
|
+
}
|
|
4347
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
const dedupChunksByName = /* @__PURE__ */ new Map();
|
|
4351
|
+
for (const chunk of chunksByName) {
|
|
4352
|
+
dedupChunksByName.set(chunk.chunkId, chunk);
|
|
4353
|
+
}
|
|
4354
|
+
for (const chunk of dedupChunksByName.values()) {
|
|
4355
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
if (filePathHint && primaryHint) {
|
|
4359
|
+
const primaryChunks = [
|
|
4360
|
+
...database.getChunksByName(primaryHint),
|
|
4361
|
+
...database.getChunksByNameCi(primaryHint)
|
|
4362
|
+
];
|
|
4363
|
+
const dedupPrimaryChunks = /* @__PURE__ */ new Map();
|
|
4364
|
+
for (const chunk of primaryChunks) {
|
|
4365
|
+
dedupPrimaryChunks.set(chunk.chunkId, chunk);
|
|
4366
|
+
}
|
|
4367
|
+
for (const chunk of dedupPrimaryChunks.values()) {
|
|
4368
|
+
if (!pathMatchesHint(chunk.filePath, filePathHint)) {
|
|
4369
|
+
continue;
|
|
4370
|
+
}
|
|
4371
|
+
const normalizedPrimary = primaryHint.replace(/_/g, "");
|
|
4372
|
+
upsertChunkCandidate(chunk, primaryHint, normalizedPrimary, 1);
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4376
|
+
if (ranked.length === 0) {
|
|
4377
|
+
const implementationFallback = fallbackCandidates.filter(
|
|
4378
|
+
(candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath(candidate.metadata.filePath)
|
|
4379
|
+
);
|
|
4380
|
+
for (const candidate of implementationFallback) {
|
|
4381
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4382
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
4383
|
+
const exactHintMatch = normalizedHints.some((hint) => nameLower === hint || nameLower.replace(/_/g, "") === hint.replace(/_/g, ""));
|
|
4384
|
+
const tokenizedName = tokenizeTextForRanking(nameLower);
|
|
4385
|
+
const tokenHits = codeTermHints.filter((term) => tokenizedName.has(term) || pathLower.includes(term)).length;
|
|
4386
|
+
if (!exactHintMatch && tokenHits === 0) {
|
|
4387
|
+
continue;
|
|
4388
|
+
}
|
|
4389
|
+
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));
|
|
4390
|
+
symbolCandidates.set(candidate.id, {
|
|
4391
|
+
id: candidate.id,
|
|
4392
|
+
score: laneScore,
|
|
4393
|
+
metadata: candidate.metadata
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
if (symbolCandidates.size === 0) {
|
|
4397
|
+
const queryTokenSet = tokenizeTextForRanking(query);
|
|
4398
|
+
const rankedFallback = implementationFallback.map((candidate) => {
|
|
4399
|
+
const nameTokens = tokenizeTextForRanking(candidate.metadata.name ?? "");
|
|
4400
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4401
|
+
let overlap = 0;
|
|
4402
|
+
for (const token of queryTokenSet) {
|
|
4403
|
+
if (nameTokens.has(token) || pathTokens.has(token)) {
|
|
4404
|
+
overlap += 1;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
const overlapScore = queryTokenSet.size > 0 ? overlap / queryTokenSet.size : 0;
|
|
4408
|
+
return {
|
|
4409
|
+
candidate,
|
|
4410
|
+
overlapScore
|
|
4411
|
+
};
|
|
4412
|
+
}).filter((entry) => entry.overlapScore > 0).sort((a, b) => b.overlapScore - a.overlapScore || b.candidate.score - a.candidate.score).slice(0, Math.max(limit, 3));
|
|
4413
|
+
for (const entry of rankedFallback) {
|
|
4414
|
+
symbolCandidates.set(entry.candidate.id, {
|
|
4415
|
+
id: entry.candidate.id,
|
|
4416
|
+
score: Math.min(0.94, Math.max(entry.candidate.score, 0.8 + entry.overlapScore * 0.1)),
|
|
4417
|
+
metadata: entry.candidate.metadata
|
|
4418
|
+
});
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4423
|
+
return withFallback.slice(0, Math.max(limit * 2, limit));
|
|
4424
|
+
}
|
|
4425
|
+
function buildIdentifierDefinitionLane(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4426
|
+
if (!prioritizeSourcePaths) {
|
|
4427
|
+
return [];
|
|
4428
|
+
}
|
|
4429
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4430
|
+
if (!primaryHint) {
|
|
4431
|
+
return [];
|
|
4432
|
+
}
|
|
4433
|
+
const hints = [primaryHint, ...extractIdentifierHints(query), ...extractCodeTermHints(query)].slice(0, 8);
|
|
4434
|
+
const scored = candidates.filter(
|
|
4435
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
4436
|
+
).map((candidate) => {
|
|
4437
|
+
const matchScore = scoreIdentifierMatch(candidate.metadata.name, candidate.metadata.filePath, hints);
|
|
4438
|
+
return {
|
|
4439
|
+
candidate,
|
|
4440
|
+
matchScore
|
|
4441
|
+
};
|
|
4442
|
+
}).filter((entry) => entry.matchScore > 0).sort((a, b) => {
|
|
4443
|
+
if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
|
|
4444
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4445
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4446
|
+
}).slice(0, Math.max(limit * 2, 10));
|
|
4447
|
+
return scored.map((entry) => ({
|
|
4448
|
+
id: entry.candidate.id,
|
|
4449
|
+
score: Math.min(1, 0.9 + entry.matchScore * 0.09),
|
|
4450
|
+
metadata: entry.candidate.metadata
|
|
4451
|
+
}));
|
|
4452
|
+
}
|
|
4453
|
+
function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
4454
|
+
if (symbolLane.length === 0) {
|
|
4455
|
+
return hybridLane.slice(0, limit);
|
|
4456
|
+
}
|
|
4457
|
+
const out = [];
|
|
4458
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4459
|
+
for (const candidate of symbolLane) {
|
|
4460
|
+
if (seen.has(candidate.id)) continue;
|
|
4461
|
+
out.push(candidate);
|
|
4462
|
+
seen.add(candidate.id);
|
|
4463
|
+
if (out.length >= limit) return out;
|
|
4464
|
+
}
|
|
4465
|
+
for (const candidate of hybridLane) {
|
|
4466
|
+
if (seen.has(candidate.id)) continue;
|
|
4467
|
+
out.push(candidate);
|
|
4468
|
+
seen.add(candidate.id);
|
|
4469
|
+
if (out.length >= limit) return out;
|
|
4470
|
+
}
|
|
4471
|
+
return out;
|
|
4472
|
+
}
|
|
4473
|
+
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
4474
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4475
|
+
for (const candidate of semanticCandidates) {
|
|
4476
|
+
byId.set(candidate.id, candidate);
|
|
4477
|
+
}
|
|
4478
|
+
for (const candidate of keywordCandidates) {
|
|
4479
|
+
const existing = byId.get(candidate.id);
|
|
4480
|
+
if (!existing || candidate.score > existing.score) {
|
|
4481
|
+
byId.set(candidate.id, candidate);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
return Array.from(byId.values());
|
|
4485
|
+
}
|
|
3480
4486
|
var Indexer = class {
|
|
3481
4487
|
config;
|
|
3482
4488
|
projectRoot;
|
|
@@ -3979,6 +4985,79 @@ var Indexer = class {
|
|
|
3979
4985
|
if (chunkDataBatch.length > 0) {
|
|
3980
4986
|
database.upsertChunksBatch(chunkDataBatch);
|
|
3981
4987
|
}
|
|
4988
|
+
const allSymbolIds = /* @__PURE__ */ new Set();
|
|
4989
|
+
const symbolsByFile = /* @__PURE__ */ new Map();
|
|
4990
|
+
for (let i = 0; i < parsedFiles.length; i++) {
|
|
4991
|
+
const parsed = parsedFiles[i];
|
|
4992
|
+
const changedFile = changedFiles[i];
|
|
4993
|
+
database.deleteCallEdgesByFile(parsed.path);
|
|
4994
|
+
database.deleteSymbolsByFile(parsed.path);
|
|
4995
|
+
const fileSymbols = [];
|
|
4996
|
+
for (const chunk of parsed.chunks) {
|
|
4997
|
+
if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
|
|
4998
|
+
const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
|
|
4999
|
+
const symbol = {
|
|
5000
|
+
id: symbolId,
|
|
5001
|
+
filePath: parsed.path,
|
|
5002
|
+
name: chunk.name,
|
|
5003
|
+
kind: chunk.chunkType,
|
|
5004
|
+
startLine: chunk.startLine,
|
|
5005
|
+
startCol: 0,
|
|
5006
|
+
endLine: chunk.endLine,
|
|
5007
|
+
endCol: 0,
|
|
5008
|
+
language: chunk.language
|
|
5009
|
+
};
|
|
5010
|
+
fileSymbols.push(symbol);
|
|
5011
|
+
allSymbolIds.add(symbolId);
|
|
5012
|
+
}
|
|
5013
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
5014
|
+
for (const symbol of fileSymbols) {
|
|
5015
|
+
const existing = symbolsByName.get(symbol.name) ?? [];
|
|
5016
|
+
existing.push(symbol);
|
|
5017
|
+
symbolsByName.set(symbol.name, existing);
|
|
5018
|
+
}
|
|
5019
|
+
if (fileSymbols.length > 0) {
|
|
5020
|
+
database.upsertSymbolsBatch(fileSymbols);
|
|
5021
|
+
symbolsByFile.set(parsed.path, fileSymbols);
|
|
5022
|
+
}
|
|
5023
|
+
const fileLanguage = parsed.chunks[0]?.language;
|
|
5024
|
+
if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
|
|
5025
|
+
const callSites = extractCalls(changedFile.content, fileLanguage);
|
|
5026
|
+
if (callSites.length === 0) continue;
|
|
5027
|
+
const edges = [];
|
|
5028
|
+
for (const site of callSites) {
|
|
5029
|
+
const enclosingSymbol = fileSymbols.find(
|
|
5030
|
+
(sym) => site.line >= sym.startLine && site.line <= sym.endLine
|
|
5031
|
+
);
|
|
5032
|
+
if (!enclosingSymbol) continue;
|
|
5033
|
+
const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
|
|
5034
|
+
edges.push({
|
|
5035
|
+
id: edgeId,
|
|
5036
|
+
fromSymbolId: enclosingSymbol.id,
|
|
5037
|
+
targetName: site.calleeName,
|
|
5038
|
+
toSymbolId: void 0,
|
|
5039
|
+
callType: site.callType,
|
|
5040
|
+
line: site.line,
|
|
5041
|
+
col: site.column,
|
|
5042
|
+
isResolved: false
|
|
5043
|
+
});
|
|
5044
|
+
}
|
|
5045
|
+
if (edges.length > 0) {
|
|
5046
|
+
database.upsertCallEdgesBatch(edges);
|
|
5047
|
+
for (const edge of edges) {
|
|
5048
|
+
const candidates = symbolsByName.get(edge.targetName);
|
|
5049
|
+
if (candidates && candidates.length === 1) {
|
|
5050
|
+
database.resolveCallEdge(edge.id, candidates[0].id);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
for (const filePath of unchangedFilePaths) {
|
|
5056
|
+
const existingSymbols = database.getSymbolsByFile(filePath);
|
|
5057
|
+
for (const sym of existingSymbols) {
|
|
5058
|
+
allSymbolIds.add(sym.id);
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
3982
5061
|
let removedCount = 0;
|
|
3983
5062
|
for (const [chunkId] of existingChunks) {
|
|
3984
5063
|
if (!currentChunkIds.has(chunkId)) {
|
|
@@ -4000,6 +5079,8 @@ var Indexer = class {
|
|
|
4000
5079
|
if (pendingChunks.length === 0 && removedCount === 0) {
|
|
4001
5080
|
database.clearBranch(this.currentBranch);
|
|
4002
5081
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5082
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5083
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4003
5084
|
this.fileHashCache = currentFileHashes;
|
|
4004
5085
|
this.saveFileHashCache();
|
|
4005
5086
|
stats.durationMs = Date.now() - startTime;
|
|
@@ -4016,6 +5097,8 @@ var Indexer = class {
|
|
|
4016
5097
|
if (pendingChunks.length === 0) {
|
|
4017
5098
|
database.clearBranch(this.currentBranch);
|
|
4018
5099
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5100
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5101
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4019
5102
|
store.save();
|
|
4020
5103
|
invertedIndex.save();
|
|
4021
5104
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4156,6 +5239,8 @@ var Indexer = class {
|
|
|
4156
5239
|
});
|
|
4157
5240
|
database.clearBranch(this.currentBranch);
|
|
4158
5241
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5242
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5243
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4159
5244
|
store.save();
|
|
4160
5245
|
invertedIndex.save();
|
|
4161
5246
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4266,15 +5351,23 @@ var Indexer = class {
|
|
|
4266
5351
|
}
|
|
4267
5352
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
4268
5353
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
5354
|
+
const fusionStrategy = this.config.search.fusionStrategy;
|
|
5355
|
+
const rrfK = this.config.search.rrfK;
|
|
5356
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
4269
5357
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5358
|
+
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
4270
5359
|
this.logger.search("debug", "Starting search", {
|
|
4271
5360
|
query,
|
|
4272
5361
|
maxResults,
|
|
4273
5362
|
hybridWeight,
|
|
5363
|
+
fusionStrategy,
|
|
5364
|
+
rrfK,
|
|
5365
|
+
rerankTopN,
|
|
4274
5366
|
filterByBranch
|
|
4275
5367
|
});
|
|
4276
5368
|
const embeddingStartTime = performance2.now();
|
|
4277
|
-
const
|
|
5369
|
+
const embeddingQuery = stripFilePathHint(query);
|
|
5370
|
+
const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
4278
5371
|
const embeddingMs = performance2.now() - embeddingStartTime;
|
|
4279
5372
|
const vectorStartTime = performance2.now();
|
|
4280
5373
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
@@ -4282,16 +5375,78 @@ var Indexer = class {
|
|
|
4282
5375
|
const keywordStartTime = performance2.now();
|
|
4283
5376
|
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
4284
5377
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
4285
|
-
const fusionStartTime = performance2.now();
|
|
4286
|
-
const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
|
|
4287
|
-
const fusionMs = performance2.now() - fusionStartTime;
|
|
4288
5378
|
let branchChunkIds = null;
|
|
4289
5379
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4290
5380
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4291
5381
|
}
|
|
4292
|
-
const
|
|
5382
|
+
const prefilterStartTime = performance2.now();
|
|
5383
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5384
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5385
|
+
const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
|
|
5386
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5387
|
+
const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
|
|
5388
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5389
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5390
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5391
|
+
branch: this.currentBranch
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5395
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5396
|
+
branch: this.currentBranch
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
|
|
5400
|
+
this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
|
|
5401
|
+
branch: this.currentBranch
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5404
|
+
const fusionStartTime = performance2.now();
|
|
5405
|
+
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
5406
|
+
fusionStrategy,
|
|
5407
|
+
rrfK,
|
|
5408
|
+
rerankTopN,
|
|
5409
|
+
limit: maxResults,
|
|
5410
|
+
hybridWeight,
|
|
5411
|
+
prioritizeSourcePaths: sourceIntent
|
|
5412
|
+
});
|
|
5413
|
+
const fusionMs = performance2.now() - fusionStartTime;
|
|
5414
|
+
const rescued = promoteIdentifierMatches(
|
|
5415
|
+
query,
|
|
5416
|
+
combined,
|
|
5417
|
+
semanticCandidates,
|
|
5418
|
+
keywordCandidates,
|
|
5419
|
+
database,
|
|
5420
|
+
branchChunkIds,
|
|
5421
|
+
sourceIntent
|
|
5422
|
+
);
|
|
5423
|
+
const union = unionCandidates(semanticCandidates, keywordCandidates);
|
|
5424
|
+
const deterministicIdentifierLane = buildDeterministicIdentifierPass(
|
|
5425
|
+
query,
|
|
5426
|
+
union,
|
|
5427
|
+
maxResults,
|
|
5428
|
+
sourceIntent
|
|
5429
|
+
);
|
|
5430
|
+
const identifierLane = buildIdentifierDefinitionLane(
|
|
5431
|
+
query,
|
|
5432
|
+
union,
|
|
5433
|
+
maxResults,
|
|
5434
|
+
sourceIntent
|
|
5435
|
+
);
|
|
5436
|
+
const symbolLane = buildSymbolDefinitionLane(
|
|
5437
|
+
query,
|
|
5438
|
+
database,
|
|
5439
|
+
branchChunkIds,
|
|
5440
|
+
maxResults,
|
|
5441
|
+
union,
|
|
5442
|
+
sourceIntent
|
|
5443
|
+
);
|
|
5444
|
+
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5445
|
+
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5446
|
+
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5447
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
|
|
5448
|
+
const baseFiltered = tiered.filter((r) => {
|
|
4293
5449
|
if (r.score < this.config.search.minScore) return false;
|
|
4294
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4295
5450
|
if (options?.fileType) {
|
|
4296
5451
|
const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
|
|
4297
5452
|
if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
|
|
@@ -4304,7 +5459,11 @@ var Indexer = class {
|
|
|
4304
5459
|
if (r.metadata.chunkType !== options.chunkType) return false;
|
|
4305
5460
|
}
|
|
4306
5461
|
return true;
|
|
4307
|
-
})
|
|
5462
|
+
});
|
|
5463
|
+
const implementationOnly = baseFiltered.filter(
|
|
5464
|
+
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5465
|
+
);
|
|
5466
|
+
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
4308
5467
|
const totalSearchMs = performance2.now() - searchStartTime;
|
|
4309
5468
|
this.logger.recordSearch(totalSearchMs, {
|
|
4310
5469
|
embeddingMs,
|
|
@@ -4319,6 +5478,7 @@ var Indexer = class {
|
|
|
4319
5478
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4320
5479
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
4321
5480
|
keywordMs: Math.round(keywordMs * 100) / 100,
|
|
5481
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100,
|
|
4322
5482
|
fusionMs: Math.round(fusionMs * 100) / 100
|
|
4323
5483
|
});
|
|
4324
5484
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
@@ -4372,34 +5532,6 @@ var Indexer = class {
|
|
|
4372
5532
|
results.sort((a, b) => b.score - a.score);
|
|
4373
5533
|
return results.slice(0, limit);
|
|
4374
5534
|
}
|
|
4375
|
-
fuseResults(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4376
|
-
const semanticWeight = 1 - keywordWeight;
|
|
4377
|
-
const fusedScores = /* @__PURE__ */ new Map();
|
|
4378
|
-
for (const r of semanticResults) {
|
|
4379
|
-
fusedScores.set(r.id, {
|
|
4380
|
-
score: r.score * semanticWeight,
|
|
4381
|
-
metadata: r.metadata
|
|
4382
|
-
});
|
|
4383
|
-
}
|
|
4384
|
-
for (const r of keywordResults) {
|
|
4385
|
-
const existing = fusedScores.get(r.id);
|
|
4386
|
-
if (existing) {
|
|
4387
|
-
existing.score += r.score * keywordWeight;
|
|
4388
|
-
} else {
|
|
4389
|
-
fusedScores.set(r.id, {
|
|
4390
|
-
score: r.score * keywordWeight,
|
|
4391
|
-
metadata: r.metadata
|
|
4392
|
-
});
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4396
|
-
id,
|
|
4397
|
-
score: data.score,
|
|
4398
|
-
metadata: data.metadata
|
|
4399
|
-
}));
|
|
4400
|
-
results.sort((a, b) => b.score - a.score);
|
|
4401
|
-
return results.slice(0, limit);
|
|
4402
|
-
}
|
|
4403
5535
|
async getStatus() {
|
|
4404
5536
|
const { store, configuredProviderInfo } = await this.ensureInitialized();
|
|
4405
5537
|
return {
|
|
@@ -4422,6 +5554,7 @@ var Indexer = class {
|
|
|
4422
5554
|
this.fileHashCache.clear();
|
|
4423
5555
|
this.saveFileHashCache();
|
|
4424
5556
|
database.clearBranch(this.currentBranch);
|
|
5557
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
4425
5558
|
database.deleteMetadata("index.version");
|
|
4426
5559
|
database.deleteMetadata("index.embeddingProvider");
|
|
4427
5560
|
database.deleteMetadata("index.embeddingModel");
|
|
@@ -4450,6 +5583,8 @@ var Indexer = class {
|
|
|
4450
5583
|
removedCount++;
|
|
4451
5584
|
}
|
|
4452
5585
|
database.deleteChunksByFile(filePath);
|
|
5586
|
+
database.deleteCallEdgesByFile(filePath);
|
|
5587
|
+
database.deleteSymbolsByFile(filePath);
|
|
4453
5588
|
removedFilePaths.push(filePath);
|
|
4454
5589
|
}
|
|
4455
5590
|
}
|
|
@@ -4459,6 +5594,8 @@ var Indexer = class {
|
|
|
4459
5594
|
}
|
|
4460
5595
|
const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
|
|
4461
5596
|
const gcOrphanChunks = database.gcOrphanChunks();
|
|
5597
|
+
const gcOrphanSymbols = database.gcOrphanSymbols();
|
|
5598
|
+
const gcOrphanCallEdges = database.gcOrphanCallEdges();
|
|
4462
5599
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
4463
5600
|
this.logger.gc("info", "Health check complete", {
|
|
4464
5601
|
removedStale: removedCount,
|
|
@@ -4466,7 +5603,7 @@ var Indexer = class {
|
|
|
4466
5603
|
orphanChunks: gcOrphanChunks,
|
|
4467
5604
|
removedFiles: removedFilePaths.length
|
|
4468
5605
|
});
|
|
4469
|
-
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
|
|
5606
|
+
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
4470
5607
|
}
|
|
4471
5608
|
async retryFailedBatches() {
|
|
4472
5609
|
const { store, provider, invertedIndex } = await this.ensureInitialized();
|
|
@@ -4572,9 +5709,29 @@ var Indexer = class {
|
|
|
4572
5709
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4573
5710
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4574
5711
|
}
|
|
4575
|
-
const
|
|
5712
|
+
const prefilterStartTime = performance2.now();
|
|
5713
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5714
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5715
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5716
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5717
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5718
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5719
|
+
branch: this.currentBranch
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5723
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5724
|
+
branch: this.currentBranch
|
|
5725
|
+
});
|
|
5726
|
+
}
|
|
5727
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
5728
|
+
const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
|
|
5729
|
+
rerankTopN,
|
|
5730
|
+
limit,
|
|
5731
|
+
prioritizeSourcePaths: false
|
|
5732
|
+
});
|
|
5733
|
+
const filtered = ranked.filter((r) => {
|
|
4576
5734
|
if (r.score < this.config.search.minScore) return false;
|
|
4577
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4578
5735
|
if (options?.excludeFile) {
|
|
4579
5736
|
if (r.metadata.filePath === options.excludeFile) return false;
|
|
4580
5737
|
}
|
|
@@ -4603,7 +5760,8 @@ var Indexer = class {
|
|
|
4603
5760
|
results: filtered.length,
|
|
4604
5761
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
4605
5762
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4606
|
-
vectorMs: Math.round(vectorMs * 100) / 100
|
|
5763
|
+
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
5764
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100
|
|
4607
5765
|
});
|
|
4608
5766
|
return Promise.all(
|
|
4609
5767
|
filtered.map(async (r) => {
|
|
@@ -4632,6 +5790,14 @@ var Indexer = class {
|
|
|
4632
5790
|
})
|
|
4633
5791
|
);
|
|
4634
5792
|
}
|
|
5793
|
+
async getCallers(targetName) {
|
|
5794
|
+
const { database } = await this.ensureInitialized();
|
|
5795
|
+
return database.getCallersWithContext(targetName, this.currentBranch);
|
|
5796
|
+
}
|
|
5797
|
+
async getCallees(symbolId) {
|
|
5798
|
+
const { database } = await this.ensureInitialized();
|
|
5799
|
+
return database.getCallees(symbolId, this.currentBranch);
|
|
5800
|
+
}
|
|
4635
5801
|
};
|
|
4636
5802
|
|
|
4637
5803
|
// node_modules/chokidar/index.js
|
|
@@ -6690,7 +7856,7 @@ ${formatted.join("\n")}
|
|
|
6690
7856
|
Use Read tool to examine specific files.`;
|
|
6691
7857
|
}
|
|
6692
7858
|
function formatHealthCheck(result) {
|
|
6693
|
-
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
|
|
7859
|
+
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
|
|
6694
7860
|
return "Index is healthy. No stale entries found.";
|
|
6695
7861
|
}
|
|
6696
7862
|
const lines = [`Health check complete:`];
|
|
@@ -6703,6 +7869,12 @@ function formatHealthCheck(result) {
|
|
|
6703
7869
|
if (result.gcOrphanChunks > 0) {
|
|
6704
7870
|
lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
|
|
6705
7871
|
}
|
|
7872
|
+
if (result.gcOrphanSymbols > 0) {
|
|
7873
|
+
lines.push(` Garbage collected orphan symbols: ${result.gcOrphanSymbols}`);
|
|
7874
|
+
}
|
|
7875
|
+
if (result.gcOrphanCallEdges > 0) {
|
|
7876
|
+
lines.push(` Garbage collected orphan call edges: ${result.gcOrphanCallEdges}`);
|
|
7877
|
+
}
|
|
6706
7878
|
if (result.filePaths.length > 0) {
|
|
6707
7879
|
lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
|
|
6708
7880
|
}
|
|
@@ -6717,6 +7889,22 @@ function formatLogs(logs) {
|
|
|
6717
7889
|
return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
|
|
6718
7890
|
}).join("\n");
|
|
6719
7891
|
}
|
|
7892
|
+
function formatDefinitionLookup(results, query) {
|
|
7893
|
+
if (results.length === 0) {
|
|
7894
|
+
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
7895
|
+
}
|
|
7896
|
+
const formatted = results.map((r, idx) => {
|
|
7897
|
+
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}`;
|
|
7898
|
+
return `${header2} (score: ${r.score.toFixed(2)})
|
|
7899
|
+
\`\`\`
|
|
7900
|
+
${truncateContent(r.content)}
|
|
7901
|
+
\`\`\``;
|
|
7902
|
+
});
|
|
7903
|
+
const header = results.length === 1 ? `Definition found for "${query}":` : `Found ${results.length} definition candidates for "${query}":`;
|
|
7904
|
+
return `${header}
|
|
7905
|
+
|
|
7906
|
+
${formatted.join("\n\n")}`;
|
|
7907
|
+
}
|
|
6720
7908
|
function formatSearchResults(results, scoreFormat = "similarity") {
|
|
6721
7909
|
const formatted = results.map((r, idx) => {
|
|
6722
7910
|
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}`;
|
|
@@ -6902,6 +8090,60 @@ var codebase_search = tool({
|
|
|
6902
8090
|
${formatSearchResults(results, "score")}`;
|
|
6903
8091
|
}
|
|
6904
8092
|
});
|
|
8093
|
+
var implementation_lookup = tool({
|
|
8094
|
+
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.",
|
|
8095
|
+
args: {
|
|
8096
|
+
query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
8097
|
+
limit: z.number().optional().default(5).describe("Maximum number of results"),
|
|
8098
|
+
fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
8099
|
+
directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
8100
|
+
},
|
|
8101
|
+
async execute(args) {
|
|
8102
|
+
const indexer = getIndexer();
|
|
8103
|
+
const results = await indexer.search(args.query, args.limit ?? 5, {
|
|
8104
|
+
fileType: args.fileType,
|
|
8105
|
+
directory: args.directory,
|
|
8106
|
+
definitionIntent: true
|
|
8107
|
+
});
|
|
8108
|
+
return formatDefinitionLookup(results, args.query);
|
|
8109
|
+
}
|
|
8110
|
+
});
|
|
8111
|
+
var call_graph = tool({
|
|
8112
|
+
description: "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies between functions.",
|
|
8113
|
+
args: {
|
|
8114
|
+
name: z.string().describe("Function or method name to query"),
|
|
8115
|
+
direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
8116
|
+
symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
|
|
8117
|
+
},
|
|
8118
|
+
async execute(args) {
|
|
8119
|
+
const indexer = getIndexer();
|
|
8120
|
+
if (args.direction === "callees") {
|
|
8121
|
+
if (!args.symbolId) {
|
|
8122
|
+
return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
|
|
8123
|
+
}
|
|
8124
|
+
const callees = await indexer.getCallees(args.symbolId);
|
|
8125
|
+
if (callees.length === 0) {
|
|
8126
|
+
return `No callees found for symbol ${args.symbolId}. The function may not call any other tracked functions.`;
|
|
8127
|
+
}
|
|
8128
|
+
const formatted2 = callees.map(
|
|
8129
|
+
(e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
|
|
8130
|
+
);
|
|
8131
|
+
return `${args.name} calls ${callees.length} function(s):
|
|
8132
|
+
|
|
8133
|
+
${formatted2.join("\n")}`;
|
|
8134
|
+
}
|
|
8135
|
+
const callers = await indexer.getCallers(args.name);
|
|
8136
|
+
if (callers.length === 0) {
|
|
8137
|
+
return `No callers found for "${args.name}". It may not be called by any tracked function, or the index needs updating.`;
|
|
8138
|
+
}
|
|
8139
|
+
const formatted = callers.map(
|
|
8140
|
+
(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]"}`
|
|
8141
|
+
);
|
|
8142
|
+
return `"${args.name}" is called by ${callers.length} function(s):
|
|
8143
|
+
|
|
8144
|
+
${formatted.join("\n")}`;
|
|
8145
|
+
}
|
|
8146
|
+
});
|
|
6905
8147
|
|
|
6906
8148
|
// src/commands/loader.ts
|
|
6907
8149
|
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
@@ -7005,7 +8247,9 @@ var plugin = async ({ directory }) => {
|
|
|
7005
8247
|
index_health_check,
|
|
7006
8248
|
index_metrics,
|
|
7007
8249
|
index_logs,
|
|
7008
|
-
find_similar
|
|
8250
|
+
find_similar,
|
|
8251
|
+
call_graph,
|
|
8252
|
+
implementation_lookup
|
|
7009
8253
|
},
|
|
7010
8254
|
async config(cfg) {
|
|
7011
8255
|
cfg.command = cfg.command ?? {};
|