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/cli.js
CHANGED
|
@@ -7,13 +7,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
7
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
8
|
var __getProtoOf = Object.getPrototypeOf;
|
|
9
9
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
-
var
|
|
11
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
-
}) : x)(function(x) {
|
|
13
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
-
});
|
|
16
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
10
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
17
11
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
12
|
};
|
|
19
13
|
var __copyProps = (to, from, except, desc) => {
|
|
@@ -35,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
29
|
|
|
36
30
|
// node_modules/eventemitter3/index.js
|
|
37
31
|
var require_eventemitter3 = __commonJS({
|
|
38
|
-
"node_modules/eventemitter3/index.js"(exports,
|
|
32
|
+
"node_modules/eventemitter3/index.js"(exports, module2) {
|
|
39
33
|
"use strict";
|
|
40
34
|
var has = Object.prototype.hasOwnProperty;
|
|
41
35
|
var prefix = "~";
|
|
@@ -189,15 +183,15 @@ var require_eventemitter3 = __commonJS({
|
|
|
189
183
|
EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
|
|
190
184
|
EventEmitter2.prefixed = prefix;
|
|
191
185
|
EventEmitter2.EventEmitter = EventEmitter2;
|
|
192
|
-
if ("undefined" !== typeof
|
|
193
|
-
|
|
186
|
+
if ("undefined" !== typeof module2) {
|
|
187
|
+
module2.exports = EventEmitter2;
|
|
194
188
|
}
|
|
195
189
|
}
|
|
196
190
|
});
|
|
197
191
|
|
|
198
192
|
// node_modules/ignore/index.js
|
|
199
193
|
var require_ignore = __commonJS({
|
|
200
|
-
"node_modules/ignore/index.js"(exports,
|
|
194
|
+
"node_modules/ignore/index.js"(exports, module2) {
|
|
201
195
|
"use strict";
|
|
202
196
|
function makeArray(subject) {
|
|
203
197
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -493,7 +487,7 @@ var require_ignore = __commonJS({
|
|
|
493
487
|
// path matching.
|
|
494
488
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
495
489
|
// @returns {TestResult} true if a file is ignored
|
|
496
|
-
test(
|
|
490
|
+
test(path10, checkUnignored, mode) {
|
|
497
491
|
let ignored = false;
|
|
498
492
|
let unignored = false;
|
|
499
493
|
let matchedRule;
|
|
@@ -502,7 +496,7 @@ var require_ignore = __commonJS({
|
|
|
502
496
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
503
497
|
return;
|
|
504
498
|
}
|
|
505
|
-
const matched = rule[mode].test(
|
|
499
|
+
const matched = rule[mode].test(path10);
|
|
506
500
|
if (!matched) {
|
|
507
501
|
return;
|
|
508
502
|
}
|
|
@@ -523,17 +517,17 @@ var require_ignore = __commonJS({
|
|
|
523
517
|
var throwError = (message, Ctor) => {
|
|
524
518
|
throw new Ctor(message);
|
|
525
519
|
};
|
|
526
|
-
var checkPath = (
|
|
527
|
-
if (!isString(
|
|
520
|
+
var checkPath = (path10, originalPath, doThrow) => {
|
|
521
|
+
if (!isString(path10)) {
|
|
528
522
|
return doThrow(
|
|
529
523
|
`path must be a string, but got \`${originalPath}\``,
|
|
530
524
|
TypeError
|
|
531
525
|
);
|
|
532
526
|
}
|
|
533
|
-
if (!
|
|
527
|
+
if (!path10) {
|
|
534
528
|
return doThrow(`path must not be empty`, TypeError);
|
|
535
529
|
}
|
|
536
|
-
if (checkPath.isNotRelative(
|
|
530
|
+
if (checkPath.isNotRelative(path10)) {
|
|
537
531
|
const r = "`path.relative()`d";
|
|
538
532
|
return doThrow(
|
|
539
533
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -542,7 +536,7 @@ var require_ignore = __commonJS({
|
|
|
542
536
|
}
|
|
543
537
|
return true;
|
|
544
538
|
};
|
|
545
|
-
var isNotRelative = (
|
|
539
|
+
var isNotRelative = (path10) => REGEX_TEST_INVALID_PATH.test(path10);
|
|
546
540
|
checkPath.isNotRelative = isNotRelative;
|
|
547
541
|
checkPath.convert = (p) => p;
|
|
548
542
|
var Ignore2 = class {
|
|
@@ -572,19 +566,19 @@ var require_ignore = __commonJS({
|
|
|
572
566
|
}
|
|
573
567
|
// @returns {TestResult}
|
|
574
568
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
575
|
-
const
|
|
569
|
+
const path10 = originalPath && checkPath.convert(originalPath);
|
|
576
570
|
checkPath(
|
|
577
|
-
|
|
571
|
+
path10,
|
|
578
572
|
originalPath,
|
|
579
573
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
580
574
|
);
|
|
581
|
-
return this._t(
|
|
575
|
+
return this._t(path10, cache, checkUnignored, slices);
|
|
582
576
|
}
|
|
583
|
-
checkIgnore(
|
|
584
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
585
|
-
return this.test(
|
|
577
|
+
checkIgnore(path10) {
|
|
578
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path10)) {
|
|
579
|
+
return this.test(path10);
|
|
586
580
|
}
|
|
587
|
-
const slices =
|
|
581
|
+
const slices = path10.split(SLASH).filter(Boolean);
|
|
588
582
|
slices.pop();
|
|
589
583
|
if (slices.length) {
|
|
590
584
|
const parent = this._t(
|
|
@@ -597,18 +591,18 @@ var require_ignore = __commonJS({
|
|
|
597
591
|
return parent;
|
|
598
592
|
}
|
|
599
593
|
}
|
|
600
|
-
return this._rules.test(
|
|
594
|
+
return this._rules.test(path10, false, MODE_CHECK_IGNORE);
|
|
601
595
|
}
|
|
602
|
-
_t(
|
|
603
|
-
if (
|
|
604
|
-
return cache[
|
|
596
|
+
_t(path10, cache, checkUnignored, slices) {
|
|
597
|
+
if (path10 in cache) {
|
|
598
|
+
return cache[path10];
|
|
605
599
|
}
|
|
606
600
|
if (!slices) {
|
|
607
|
-
slices =
|
|
601
|
+
slices = path10.split(SLASH).filter(Boolean);
|
|
608
602
|
}
|
|
609
603
|
slices.pop();
|
|
610
604
|
if (!slices.length) {
|
|
611
|
-
return cache[
|
|
605
|
+
return cache[path10] = this._rules.test(path10, checkUnignored, MODE_IGNORE);
|
|
612
606
|
}
|
|
613
607
|
const parent = this._t(
|
|
614
608
|
slices.join(SLASH) + SLASH,
|
|
@@ -616,29 +610,29 @@ var require_ignore = __commonJS({
|
|
|
616
610
|
checkUnignored,
|
|
617
611
|
slices
|
|
618
612
|
);
|
|
619
|
-
return cache[
|
|
613
|
+
return cache[path10] = parent.ignored ? parent : this._rules.test(path10, checkUnignored, MODE_IGNORE);
|
|
620
614
|
}
|
|
621
|
-
ignores(
|
|
622
|
-
return this._test(
|
|
615
|
+
ignores(path10) {
|
|
616
|
+
return this._test(path10, this._ignoreCache, false).ignored;
|
|
623
617
|
}
|
|
624
618
|
createFilter() {
|
|
625
|
-
return (
|
|
619
|
+
return (path10) => !this.ignores(path10);
|
|
626
620
|
}
|
|
627
621
|
filter(paths) {
|
|
628
622
|
return makeArray(paths).filter(this.createFilter());
|
|
629
623
|
}
|
|
630
624
|
// @returns {TestResult}
|
|
631
|
-
test(
|
|
632
|
-
return this._test(
|
|
625
|
+
test(path10) {
|
|
626
|
+
return this._test(path10, this._testCache, true);
|
|
633
627
|
}
|
|
634
628
|
};
|
|
635
629
|
var factory = (options) => new Ignore2(options);
|
|
636
|
-
var isPathValid = (
|
|
630
|
+
var isPathValid = (path10) => checkPath(path10 && checkPath.convert(path10), path10, RETURN_FALSE);
|
|
637
631
|
var setupWindows = () => {
|
|
638
632
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
639
633
|
checkPath.convert = makePosix;
|
|
640
634
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
641
|
-
checkPath.isNotRelative = (
|
|
635
|
+
checkPath.isNotRelative = (path10) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path10) || isNotRelative(path10);
|
|
642
636
|
};
|
|
643
637
|
if (
|
|
644
638
|
// Detect `process` so that it can run in browsers.
|
|
@@ -646,18 +640,18 @@ var require_ignore = __commonJS({
|
|
|
646
640
|
) {
|
|
647
641
|
setupWindows();
|
|
648
642
|
}
|
|
649
|
-
|
|
643
|
+
module2.exports = factory;
|
|
650
644
|
factory.default = factory;
|
|
651
|
-
|
|
652
|
-
define(
|
|
645
|
+
module2.exports.isPathValid = isPathValid;
|
|
646
|
+
define(module2.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
653
647
|
}
|
|
654
648
|
});
|
|
655
649
|
|
|
656
650
|
// src/cli.ts
|
|
657
651
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
658
|
-
import { existsSync as
|
|
659
|
-
import * as
|
|
660
|
-
import * as
|
|
652
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
653
|
+
import * as path9 from "path";
|
|
654
|
+
import * as os4 from "os";
|
|
661
655
|
|
|
662
656
|
// src/config/constants.ts
|
|
663
657
|
var DEFAULT_INCLUDE = [
|
|
@@ -665,7 +659,7 @@ var DEFAULT_INCLUDE = [
|
|
|
665
659
|
"**/*.{py,pyi}",
|
|
666
660
|
"**/*.{go,rs,java,kt,scala}",
|
|
667
661
|
"**/*.{c,cpp,cc,h,hpp}",
|
|
668
|
-
"**/*.{rb,php,swift}",
|
|
662
|
+
"**/*.{rb,php,inc,swift}",
|
|
669
663
|
"**/*.{vue,svelte,astro}",
|
|
670
664
|
"**/*.{sql,graphql,proto}",
|
|
671
665
|
"**/*.{yaml,yml,toml}",
|
|
@@ -782,9 +776,15 @@ function getDefaultSearchConfig() {
|
|
|
782
776
|
minScore: 0.1,
|
|
783
777
|
includeContext: true,
|
|
784
778
|
hybridWeight: 0.5,
|
|
779
|
+
fusionStrategy: "rrf",
|
|
780
|
+
rrfK: 60,
|
|
781
|
+
rerankTopN: 20,
|
|
785
782
|
contextLines: 0
|
|
786
783
|
};
|
|
787
784
|
}
|
|
785
|
+
function isValidFusionStrategy(value) {
|
|
786
|
+
return value === "weighted" || value === "rrf";
|
|
787
|
+
}
|
|
788
788
|
function getDefaultDebugConfig() {
|
|
789
789
|
return {
|
|
790
790
|
enabled: false,
|
|
@@ -839,6 +839,9 @@ function parseConfig(raw) {
|
|
|
839
839
|
minScore: typeof rawSearch.minScore === "number" ? rawSearch.minScore : defaultSearch.minScore,
|
|
840
840
|
includeContext: typeof rawSearch.includeContext === "boolean" ? rawSearch.includeContext : defaultSearch.includeContext,
|
|
841
841
|
hybridWeight: typeof rawSearch.hybridWeight === "number" ? Math.min(1, Math.max(0, rawSearch.hybridWeight)) : defaultSearch.hybridWeight,
|
|
842
|
+
fusionStrategy: isValidFusionStrategy(rawSearch.fusionStrategy) ? rawSearch.fusionStrategy : defaultSearch.fusionStrategy,
|
|
843
|
+
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
844
|
+
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
842
845
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
|
|
843
846
|
};
|
|
844
847
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -906,13 +909,197 @@ function getDefaultModelForProvider(provider) {
|
|
|
906
909
|
}
|
|
907
910
|
var availableProviders = Object.keys(EMBEDDING_MODELS);
|
|
908
911
|
|
|
909
|
-
// src/
|
|
910
|
-
import
|
|
911
|
-
|
|
912
|
+
// src/eval/cli.ts
|
|
913
|
+
import * as path8 from "path";
|
|
914
|
+
|
|
915
|
+
// src/eval/compare.ts
|
|
916
|
+
function metricDelta(current, baseline) {
|
|
917
|
+
const absolute = current - baseline;
|
|
918
|
+
const relativePct = baseline === 0 ? current === 0 ? 0 : 100 : absolute / baseline * 100;
|
|
919
|
+
return {
|
|
920
|
+
current,
|
|
921
|
+
baseline,
|
|
922
|
+
absolute,
|
|
923
|
+
relativePct
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function compareSummaries(current, baseline, againstPath) {
|
|
927
|
+
return {
|
|
928
|
+
againstPath,
|
|
929
|
+
deltas: {
|
|
930
|
+
hitAt1: metricDelta(current.metrics.hitAt1, baseline.metrics.hitAt1),
|
|
931
|
+
hitAt3: metricDelta(current.metrics.hitAt3, baseline.metrics.hitAt3),
|
|
932
|
+
hitAt5: metricDelta(current.metrics.hitAt5, baseline.metrics.hitAt5),
|
|
933
|
+
hitAt10: metricDelta(current.metrics.hitAt10, baseline.metrics.hitAt10),
|
|
934
|
+
mrrAt10: metricDelta(current.metrics.mrrAt10, baseline.metrics.mrrAt10),
|
|
935
|
+
ndcgAt10: metricDelta(current.metrics.ndcgAt10, baseline.metrics.ndcgAt10),
|
|
936
|
+
latencyP50Ms: metricDelta(current.metrics.latencyMs.p50, baseline.metrics.latencyMs.p50),
|
|
937
|
+
latencyP95Ms: metricDelta(current.metrics.latencyMs.p95, baseline.metrics.latencyMs.p95),
|
|
938
|
+
latencyP99Ms: metricDelta(current.metrics.latencyMs.p99, baseline.metrics.latencyMs.p99),
|
|
939
|
+
embeddingCallCount: metricDelta(
|
|
940
|
+
current.metrics.embedding.callCount,
|
|
941
|
+
baseline.metrics.embedding.callCount
|
|
942
|
+
),
|
|
943
|
+
estimatedCostUsd: metricDelta(
|
|
944
|
+
current.metrics.embedding.estimatedCostUsd,
|
|
945
|
+
baseline.metrics.embedding.estimatedCostUsd
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/eval/reports.ts
|
|
952
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
953
|
+
import * as path from "path";
|
|
954
|
+
function formatPct(value) {
|
|
955
|
+
return `${(value * 100).toFixed(2)}%`;
|
|
956
|
+
}
|
|
957
|
+
function formatMs(value) {
|
|
958
|
+
return `${value.toFixed(3)}ms`;
|
|
959
|
+
}
|
|
960
|
+
function formatUsd(value) {
|
|
961
|
+
return `$${value.toFixed(6)}`;
|
|
962
|
+
}
|
|
963
|
+
function signed(value, digits = 4) {
|
|
964
|
+
const formatted = value.toFixed(digits);
|
|
965
|
+
return value > 0 ? `+${formatted}` : formatted;
|
|
966
|
+
}
|
|
967
|
+
function loadSummary(summaryPath) {
|
|
968
|
+
const raw = readFileSync(summaryPath, "utf-8");
|
|
969
|
+
return JSON.parse(raw);
|
|
970
|
+
}
|
|
971
|
+
function createRunDirectory(outputRoot, timestampOverride) {
|
|
972
|
+
const timestamp = (timestampOverride ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
|
|
973
|
+
const dir = path.join(outputRoot, timestamp);
|
|
974
|
+
mkdirSync(dir, { recursive: true });
|
|
975
|
+
return dir;
|
|
976
|
+
}
|
|
977
|
+
function writeJson(filePath, value) {
|
|
978
|
+
writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
979
|
+
}
|
|
980
|
+
function writeText(filePath, value) {
|
|
981
|
+
writeFileSync(filePath, value, "utf-8");
|
|
982
|
+
}
|
|
983
|
+
function createSummaryMarkdown(summary, comparison, gate, sweep) {
|
|
984
|
+
const lines = [];
|
|
985
|
+
lines.push("# Evaluation Summary");
|
|
986
|
+
lines.push("");
|
|
987
|
+
lines.push(`- Generated: ${summary.generatedAt}`);
|
|
988
|
+
lines.push(`- Dataset: ${summary.datasetName} (v${summary.datasetVersion})`);
|
|
989
|
+
lines.push(`- Query count: ${summary.queryCount}`);
|
|
990
|
+
lines.push(
|
|
991
|
+
`- Search config: fusion=${summary.searchConfig.fusionStrategy}, hybridWeight=${summary.searchConfig.hybridWeight}, rrfK=${summary.searchConfig.rrfK}, rerankTopN=${summary.searchConfig.rerankTopN}`
|
|
992
|
+
);
|
|
993
|
+
lines.push("");
|
|
994
|
+
lines.push("## Metrics");
|
|
995
|
+
lines.push("");
|
|
996
|
+
lines.push("| Metric | Value |");
|
|
997
|
+
lines.push("|---|---:|");
|
|
998
|
+
lines.push(`| Hit@1 | ${formatPct(summary.metrics.hitAt1)} |`);
|
|
999
|
+
lines.push(`| Hit@3 | ${formatPct(summary.metrics.hitAt3)} |`);
|
|
1000
|
+
lines.push(`| Hit@5 | ${formatPct(summary.metrics.hitAt5)} |`);
|
|
1001
|
+
lines.push(`| Hit@10 | ${formatPct(summary.metrics.hitAt10)} |`);
|
|
1002
|
+
lines.push(`| MRR@10 | ${summary.metrics.mrrAt10.toFixed(4)} |`);
|
|
1003
|
+
lines.push(`| nDCG@10 | ${summary.metrics.ndcgAt10.toFixed(4)} |`);
|
|
1004
|
+
lines.push(`| Latency p50 | ${formatMs(summary.metrics.latencyMs.p50)} |`);
|
|
1005
|
+
lines.push(`| Latency p95 | ${formatMs(summary.metrics.latencyMs.p95)} |`);
|
|
1006
|
+
lines.push(`| Latency p99 | ${formatMs(summary.metrics.latencyMs.p99)} |`);
|
|
1007
|
+
lines.push(`| Embedding calls | ${summary.metrics.embedding.callCount} |`);
|
|
1008
|
+
lines.push(`| Embedding tokens | ${summary.metrics.tokenEstimate.embeddingTokensUsed} |`);
|
|
1009
|
+
lines.push(`| Estimated embedding cost | ${formatUsd(summary.metrics.embedding.estimatedCostUsd)} |`);
|
|
1010
|
+
lines.push("");
|
|
1011
|
+
lines.push("## Failure Buckets");
|
|
1012
|
+
lines.push("");
|
|
1013
|
+
lines.push("| Bucket | Count |");
|
|
1014
|
+
lines.push("|---|---:|");
|
|
1015
|
+
lines.push(
|
|
1016
|
+
`| wrong-file | ${summary.metrics.failureBuckets["wrong-file"]} |`
|
|
1017
|
+
);
|
|
1018
|
+
lines.push(
|
|
1019
|
+
`| wrong-symbol | ${summary.metrics.failureBuckets["wrong-symbol"]} |`
|
|
1020
|
+
);
|
|
1021
|
+
lines.push(
|
|
1022
|
+
`| docs/tests outranking source | ${summary.metrics.failureBuckets["docs-tests-outranking-source"]} |`
|
|
1023
|
+
);
|
|
1024
|
+
lines.push(
|
|
1025
|
+
`| no relevant hit in top-k | ${summary.metrics.failureBuckets["no-relevant-hit-top-k"]} |`
|
|
1026
|
+
);
|
|
1027
|
+
lines.push("");
|
|
1028
|
+
if (comparison) {
|
|
1029
|
+
lines.push("## Comparison vs Baseline");
|
|
1030
|
+
lines.push("");
|
|
1031
|
+
lines.push(`- Against: ${comparison.againstPath}`);
|
|
1032
|
+
lines.push("");
|
|
1033
|
+
lines.push("| Metric | Baseline | Current | Delta |");
|
|
1034
|
+
lines.push("|---|---:|---:|---:|");
|
|
1035
|
+
lines.push(
|
|
1036
|
+
`| Hit@5 | ${formatPct(comparison.deltas.hitAt5.baseline)} | ${formatPct(comparison.deltas.hitAt5.current)} | ${signed(comparison.deltas.hitAt5.absolute)} |`
|
|
1037
|
+
);
|
|
1038
|
+
lines.push(
|
|
1039
|
+
`| MRR@10 | ${comparison.deltas.mrrAt10.baseline.toFixed(4)} | ${comparison.deltas.mrrAt10.current.toFixed(4)} | ${signed(comparison.deltas.mrrAt10.absolute)} |`
|
|
1040
|
+
);
|
|
1041
|
+
lines.push(
|
|
1042
|
+
`| nDCG@10 | ${comparison.deltas.ndcgAt10.baseline.toFixed(4)} | ${comparison.deltas.ndcgAt10.current.toFixed(4)} | ${signed(comparison.deltas.ndcgAt10.absolute)} |`
|
|
1043
|
+
);
|
|
1044
|
+
lines.push(
|
|
1045
|
+
`| p95 latency (ms) | ${comparison.deltas.latencyP95Ms.baseline.toFixed(3)} | ${comparison.deltas.latencyP95Ms.current.toFixed(3)} | ${signed(comparison.deltas.latencyP95Ms.absolute, 3)} |`
|
|
1046
|
+
);
|
|
1047
|
+
lines.push("");
|
|
1048
|
+
}
|
|
1049
|
+
if (gate) {
|
|
1050
|
+
lines.push("## CI Gate");
|
|
1051
|
+
lines.push("");
|
|
1052
|
+
lines.push(`- Result: ${gate.passed ? "PASS \u2705" : "FAIL \u274C"}`);
|
|
1053
|
+
if (gate.violations.length > 0) {
|
|
1054
|
+
lines.push("- Violations:");
|
|
1055
|
+
for (const violation of gate.violations) {
|
|
1056
|
+
lines.push(` - ${violation.metric}: ${violation.message}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
lines.push("");
|
|
1060
|
+
}
|
|
1061
|
+
if (sweep) {
|
|
1062
|
+
lines.push("## Parameter Sweep");
|
|
1063
|
+
lines.push("");
|
|
1064
|
+
lines.push(`- Run count: ${sweep.runCount}`);
|
|
1065
|
+
if (sweep.bestByHitAt5) {
|
|
1066
|
+
lines.push(
|
|
1067
|
+
`- Best Hit@5: ${formatPct(sweep.bestByHitAt5.summary.metrics.hitAt5)} with fusion=${sweep.bestByHitAt5.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByHitAt5.searchConfig.hybridWeight}, rrfK=${sweep.bestByHitAt5.searchConfig.rrfK}, rerankTopN=${sweep.bestByHitAt5.searchConfig.rerankTopN}`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (sweep.bestByMrrAt10) {
|
|
1071
|
+
lines.push(
|
|
1072
|
+
`- Best MRR@10: ${sweep.bestByMrrAt10.summary.metrics.mrrAt10.toFixed(4)} with fusion=${sweep.bestByMrrAt10.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByMrrAt10.searchConfig.hybridWeight}, rrfK=${sweep.bestByMrrAt10.searchConfig.rrfK}, rerankTopN=${sweep.bestByMrrAt10.searchConfig.rerankTopN}`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (sweep.bestByP95Latency) {
|
|
1076
|
+
lines.push(
|
|
1077
|
+
`- Best p95 latency: ${formatMs(sweep.bestByP95Latency.summary.metrics.latencyMs.p95)} with fusion=${sweep.bestByP95Latency.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByP95Latency.searchConfig.hybridWeight}, rrfK=${sweep.bestByP95Latency.searchConfig.rrfK}, rerankTopN=${sweep.bestByP95Latency.searchConfig.rerankTopN}`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
lines.push("");
|
|
1081
|
+
}
|
|
1082
|
+
return `${lines.join("\n")}
|
|
1083
|
+
`;
|
|
1084
|
+
}
|
|
1085
|
+
function buildPerQueryArtifact(perQuery) {
|
|
1086
|
+
return {
|
|
1087
|
+
queryCount: perQuery.length,
|
|
1088
|
+
queries: [...perQuery].sort((a, b) => a.id.localeCompare(b.id))
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// src/eval/runner.ts
|
|
1093
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1094
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
1095
|
+
import { rmSync } from "fs";
|
|
1096
|
+
import * as os3 from "os";
|
|
1097
|
+
import * as path7 from "path";
|
|
1098
|
+
import { performance as performance3 } from "perf_hooks";
|
|
912
1099
|
|
|
913
1100
|
// src/indexer/index.ts
|
|
914
|
-
import { existsSync as existsSync4, readFileSync as
|
|
915
|
-
import * as
|
|
1101
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
|
|
1102
|
+
import * as path6 from "path";
|
|
916
1103
|
import { performance as performance2 } from "perf_hooks";
|
|
917
1104
|
|
|
918
1105
|
// node_modules/eventemitter3/index.mjs
|
|
@@ -937,7 +1124,7 @@ function pTimeout(promise, options) {
|
|
|
937
1124
|
} = options;
|
|
938
1125
|
let timer;
|
|
939
1126
|
let abortHandler;
|
|
940
|
-
const wrappedPromise = new Promise((
|
|
1127
|
+
const wrappedPromise = new Promise((resolve5, reject) => {
|
|
941
1128
|
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
942
1129
|
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
943
1130
|
}
|
|
@@ -951,7 +1138,7 @@ function pTimeout(promise, options) {
|
|
|
951
1138
|
};
|
|
952
1139
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
953
1140
|
}
|
|
954
|
-
promise.then(
|
|
1141
|
+
promise.then(resolve5, reject);
|
|
955
1142
|
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
956
1143
|
return;
|
|
957
1144
|
}
|
|
@@ -959,7 +1146,7 @@ function pTimeout(promise, options) {
|
|
|
959
1146
|
timer = customTimers.setTimeout.call(void 0, () => {
|
|
960
1147
|
if (fallback) {
|
|
961
1148
|
try {
|
|
962
|
-
|
|
1149
|
+
resolve5(fallback());
|
|
963
1150
|
} catch (error) {
|
|
964
1151
|
reject(error);
|
|
965
1152
|
}
|
|
@@ -969,7 +1156,7 @@ function pTimeout(promise, options) {
|
|
|
969
1156
|
promise.cancel();
|
|
970
1157
|
}
|
|
971
1158
|
if (message === false) {
|
|
972
|
-
|
|
1159
|
+
resolve5();
|
|
973
1160
|
} else if (message instanceof Error) {
|
|
974
1161
|
reject(message);
|
|
975
1162
|
} else {
|
|
@@ -1359,7 +1546,7 @@ var PQueue = class extends import_index.default {
|
|
|
1359
1546
|
// Assign unique ID if not provided
|
|
1360
1547
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
1361
1548
|
};
|
|
1362
|
-
return new Promise((
|
|
1549
|
+
return new Promise((resolve5, reject) => {
|
|
1363
1550
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
1364
1551
|
this.#queue.enqueue(async () => {
|
|
1365
1552
|
this.#pending++;
|
|
@@ -1397,7 +1584,7 @@ var PQueue = class extends import_index.default {
|
|
|
1397
1584
|
})]);
|
|
1398
1585
|
}
|
|
1399
1586
|
const result = await operation;
|
|
1400
|
-
|
|
1587
|
+
resolve5(result);
|
|
1401
1588
|
this.emit("completed", result);
|
|
1402
1589
|
} catch (error) {
|
|
1403
1590
|
reject(error);
|
|
@@ -1554,13 +1741,13 @@ var PQueue = class extends import_index.default {
|
|
|
1554
1741
|
});
|
|
1555
1742
|
}
|
|
1556
1743
|
async #onEvent(event, filter) {
|
|
1557
|
-
return new Promise((
|
|
1744
|
+
return new Promise((resolve5) => {
|
|
1558
1745
|
const listener = () => {
|
|
1559
1746
|
if (filter && !filter()) {
|
|
1560
1747
|
return;
|
|
1561
1748
|
}
|
|
1562
1749
|
this.off(event, listener);
|
|
1563
|
-
|
|
1750
|
+
resolve5();
|
|
1564
1751
|
};
|
|
1565
1752
|
this.on(event, listener);
|
|
1566
1753
|
});
|
|
@@ -1845,7 +2032,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
1845
2032
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
1846
2033
|
options.signal?.throwIfAborted();
|
|
1847
2034
|
if (finalDelay > 0) {
|
|
1848
|
-
await new Promise((
|
|
2035
|
+
await new Promise((resolve5, reject) => {
|
|
1849
2036
|
const onAbort = () => {
|
|
1850
2037
|
clearTimeout(timeoutToken);
|
|
1851
2038
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -1853,7 +2040,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
1853
2040
|
};
|
|
1854
2041
|
const timeoutToken = setTimeout(() => {
|
|
1855
2042
|
options.signal?.removeEventListener("abort", onAbort);
|
|
1856
|
-
|
|
2043
|
+
resolve5();
|
|
1857
2044
|
}, finalDelay);
|
|
1858
2045
|
if (options.unref) {
|
|
1859
2046
|
timeoutToken.unref?.();
|
|
@@ -1914,17 +2101,17 @@ async function pRetry(input, options = {}) {
|
|
|
1914
2101
|
}
|
|
1915
2102
|
|
|
1916
2103
|
// src/embeddings/detector.ts
|
|
1917
|
-
import { existsSync, readFileSync } from "fs";
|
|
1918
|
-
import * as
|
|
2104
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
2105
|
+
import * as path2 from "path";
|
|
1919
2106
|
import * as os from "os";
|
|
1920
2107
|
function getOpenCodeAuthPath() {
|
|
1921
|
-
return
|
|
2108
|
+
return path2.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
1922
2109
|
}
|
|
1923
2110
|
function loadOpenCodeAuth() {
|
|
1924
2111
|
const authPath = getOpenCodeAuthPath();
|
|
1925
2112
|
try {
|
|
1926
2113
|
if (existsSync(authPath)) {
|
|
1927
|
-
return JSON.parse(
|
|
2114
|
+
return JSON.parse(readFileSync2(authPath, "utf-8"));
|
|
1928
2115
|
}
|
|
1929
2116
|
} catch {
|
|
1930
2117
|
}
|
|
@@ -2426,8 +2613,8 @@ var CustomEmbeddingProvider = class {
|
|
|
2426
2613
|
|
|
2427
2614
|
// src/utils/files.ts
|
|
2428
2615
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2429
|
-
import { existsSync as existsSync2, readFileSync as
|
|
2430
|
-
import * as
|
|
2616
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, promises as fsPromises } from "fs";
|
|
2617
|
+
import * as path3 from "path";
|
|
2431
2618
|
function createIgnoreFilter(projectRoot) {
|
|
2432
2619
|
const ig = (0, import_ignore.default)();
|
|
2433
2620
|
const defaultIgnores = [
|
|
@@ -2444,9 +2631,9 @@ function createIgnoreFilter(projectRoot) {
|
|
|
2444
2631
|
".opencode"
|
|
2445
2632
|
];
|
|
2446
2633
|
ig.add(defaultIgnores);
|
|
2447
|
-
const gitignorePath =
|
|
2634
|
+
const gitignorePath = path3.join(projectRoot, ".gitignore");
|
|
2448
2635
|
if (existsSync2(gitignorePath)) {
|
|
2449
|
-
const gitignoreContent =
|
|
2636
|
+
const gitignoreContent = readFileSync3(gitignorePath, "utf-8");
|
|
2450
2637
|
ig.add(gitignoreContent);
|
|
2451
2638
|
}
|
|
2452
2639
|
return ig;
|
|
@@ -2462,8 +2649,8 @@ function matchGlob(filePath, pattern) {
|
|
|
2462
2649
|
async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped) {
|
|
2463
2650
|
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
2464
2651
|
for (const entry of entries) {
|
|
2465
|
-
const fullPath =
|
|
2466
|
-
const relativePath =
|
|
2652
|
+
const fullPath = path3.join(dir, entry.name);
|
|
2653
|
+
const relativePath = path3.relative(projectRoot, fullPath);
|
|
2467
2654
|
if (ignoreFilter.ignores(relativePath)) {
|
|
2468
2655
|
if (entry.isFile()) {
|
|
2469
2656
|
skipped.push({ path: relativePath, reason: "gitignore" });
|
|
@@ -2524,6 +2711,9 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
|
|
|
2524
2711
|
}
|
|
2525
2712
|
|
|
2526
2713
|
// src/utils/cost.ts
|
|
2714
|
+
function estimateTokens(text) {
|
|
2715
|
+
return Math.ceil(text.length / 4);
|
|
2716
|
+
}
|
|
2527
2717
|
function estimateChunksFromFiles(files) {
|
|
2528
2718
|
let totalChunks = 0;
|
|
2529
2719
|
for (const file of files) {
|
|
@@ -2878,8 +3068,9 @@ function initializeLogger(config) {
|
|
|
2878
3068
|
}
|
|
2879
3069
|
|
|
2880
3070
|
// src/native/index.ts
|
|
2881
|
-
import * as
|
|
3071
|
+
import * as path4 from "path";
|
|
2882
3072
|
import * as os2 from "os";
|
|
3073
|
+
import * as module from "module";
|
|
2883
3074
|
import { fileURLToPath } from "url";
|
|
2884
3075
|
function getNativeBinding() {
|
|
2885
3076
|
const platform2 = os2.platform();
|
|
@@ -2899,17 +3090,22 @@ function getNativeBinding() {
|
|
|
2899
3090
|
throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
|
|
2900
3091
|
}
|
|
2901
3092
|
let currentDir;
|
|
3093
|
+
let requireTarget;
|
|
2902
3094
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
2903
|
-
currentDir =
|
|
3095
|
+
currentDir = path4.dirname(fileURLToPath(import.meta.url));
|
|
3096
|
+
requireTarget = import.meta.url;
|
|
2904
3097
|
} else if (typeof __dirname !== "undefined") {
|
|
2905
3098
|
currentDir = __dirname;
|
|
3099
|
+
requireTarget = __filename;
|
|
2906
3100
|
} else {
|
|
2907
3101
|
currentDir = process.cwd();
|
|
3102
|
+
requireTarget = path4.join(currentDir, "index.js");
|
|
2908
3103
|
}
|
|
2909
3104
|
const isDevMode = currentDir.includes("/src/native");
|
|
2910
|
-
const packageRoot = isDevMode ?
|
|
2911
|
-
const nativePath =
|
|
2912
|
-
|
|
3105
|
+
const packageRoot = isDevMode ? path4.resolve(currentDir, "../..") : path4.resolve(currentDir, "..");
|
|
3106
|
+
const nativePath = path4.join(packageRoot, "native", bindingName);
|
|
3107
|
+
const require2 = module.createRequire(requireTarget);
|
|
3108
|
+
return require2(nativePath);
|
|
2913
3109
|
}
|
|
2914
3110
|
var native = getNativeBinding();
|
|
2915
3111
|
function parseFiles(files) {
|
|
@@ -2936,6 +3132,9 @@ function hashContent(content) {
|
|
|
2936
3132
|
function hashFile(filePath) {
|
|
2937
3133
|
return native.hashFile(filePath);
|
|
2938
3134
|
}
|
|
3135
|
+
function extractCalls(content, language) {
|
|
3136
|
+
return native.extractCalls(content, language);
|
|
3137
|
+
}
|
|
2939
3138
|
var VectorStore = class {
|
|
2940
3139
|
inner;
|
|
2941
3140
|
dimensions;
|
|
@@ -3024,7 +3223,7 @@ var VectorStore = class {
|
|
|
3024
3223
|
var CHARS_PER_TOKEN = 4;
|
|
3025
3224
|
var MAX_BATCH_TOKENS = 7500;
|
|
3026
3225
|
var MAX_SINGLE_CHUNK_TOKENS = 2e3;
|
|
3027
|
-
function
|
|
3226
|
+
function estimateTokens2(text) {
|
|
3028
3227
|
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3029
3228
|
}
|
|
3030
3229
|
function createEmbeddingText(chunk, filePath) {
|
|
@@ -3089,7 +3288,7 @@ function createDynamicBatches(chunks) {
|
|
|
3089
3288
|
let currentBatch = [];
|
|
3090
3289
|
let currentTokens = 0;
|
|
3091
3290
|
for (const chunk of chunks) {
|
|
3092
|
-
const chunkTokens =
|
|
3291
|
+
const chunkTokens = estimateTokens2(chunk.text);
|
|
3093
3292
|
if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
|
|
3094
3293
|
batches.push(currentBatch);
|
|
3095
3294
|
currentBatch = [];
|
|
@@ -3267,6 +3466,12 @@ var Database = class {
|
|
|
3267
3466
|
getChunksByFile(filePath) {
|
|
3268
3467
|
return this.inner.getChunksByFile(filePath);
|
|
3269
3468
|
}
|
|
3469
|
+
getChunksByName(name) {
|
|
3470
|
+
return this.inner.getChunksByName(name);
|
|
3471
|
+
}
|
|
3472
|
+
getChunksByNameCi(name) {
|
|
3473
|
+
return this.inner.getChunksByNameCi(name);
|
|
3474
|
+
}
|
|
3270
3475
|
deleteChunksByFile(filePath) {
|
|
3271
3476
|
return this.inner.deleteChunksByFile(filePath);
|
|
3272
3477
|
}
|
|
@@ -3310,14 +3515,110 @@ var Database = class {
|
|
|
3310
3515
|
getStats() {
|
|
3311
3516
|
return this.inner.getStats();
|
|
3312
3517
|
}
|
|
3518
|
+
// ── Symbol methods ──────────────────────────────────────────────
|
|
3519
|
+
upsertSymbol(symbol) {
|
|
3520
|
+
this.inner.upsertSymbol(symbol);
|
|
3521
|
+
}
|
|
3522
|
+
upsertSymbolsBatch(symbols) {
|
|
3523
|
+
if (symbols.length === 0) return;
|
|
3524
|
+
this.inner.upsertSymbolsBatch(symbols);
|
|
3525
|
+
}
|
|
3526
|
+
getSymbolsByFile(filePath) {
|
|
3527
|
+
return this.inner.getSymbolsByFile(filePath);
|
|
3528
|
+
}
|
|
3529
|
+
getSymbolByName(name, filePath) {
|
|
3530
|
+
return this.inner.getSymbolByName(name, filePath) ?? null;
|
|
3531
|
+
}
|
|
3532
|
+
getSymbolsByName(name) {
|
|
3533
|
+
return this.inner.getSymbolsByName(name);
|
|
3534
|
+
}
|
|
3535
|
+
getSymbolsByNameCi(name) {
|
|
3536
|
+
return this.inner.getSymbolsByNameCi(name);
|
|
3537
|
+
}
|
|
3538
|
+
deleteSymbolsByFile(filePath) {
|
|
3539
|
+
return this.inner.deleteSymbolsByFile(filePath);
|
|
3540
|
+
}
|
|
3541
|
+
// ── Call Edge methods ────────────────────────────────────────────
|
|
3542
|
+
upsertCallEdge(edge) {
|
|
3543
|
+
this.inner.upsertCallEdge(edge);
|
|
3544
|
+
}
|
|
3545
|
+
upsertCallEdgesBatch(edges) {
|
|
3546
|
+
if (edges.length === 0) return;
|
|
3547
|
+
this.inner.upsertCallEdgesBatch(edges);
|
|
3548
|
+
}
|
|
3549
|
+
getCallers(targetName, branch) {
|
|
3550
|
+
return this.inner.getCallers(targetName, branch);
|
|
3551
|
+
}
|
|
3552
|
+
getCallersWithContext(targetName, branch) {
|
|
3553
|
+
return this.inner.getCallersWithContext(targetName, branch);
|
|
3554
|
+
}
|
|
3555
|
+
getCallees(symbolId, branch) {
|
|
3556
|
+
return this.inner.getCallees(symbolId, branch);
|
|
3557
|
+
}
|
|
3558
|
+
deleteCallEdgesByFile(filePath) {
|
|
3559
|
+
return this.inner.deleteCallEdgesByFile(filePath);
|
|
3560
|
+
}
|
|
3561
|
+
resolveCallEdge(edgeId, toSymbolId) {
|
|
3562
|
+
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
3563
|
+
}
|
|
3564
|
+
// ── Branch Symbol methods ────────────────────────────────────────
|
|
3565
|
+
addSymbolsToBranch(branch, symbolIds) {
|
|
3566
|
+
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
3567
|
+
}
|
|
3568
|
+
addSymbolsToBranchBatch(branch, symbolIds) {
|
|
3569
|
+
if (symbolIds.length === 0) return;
|
|
3570
|
+
this.inner.addSymbolsToBranchBatch(branch, symbolIds);
|
|
3571
|
+
}
|
|
3572
|
+
getBranchSymbolIds(branch) {
|
|
3573
|
+
return this.inner.getBranchSymbolIds(branch);
|
|
3574
|
+
}
|
|
3575
|
+
clearBranchSymbols(branch) {
|
|
3576
|
+
return this.inner.clearBranchSymbols(branch);
|
|
3577
|
+
}
|
|
3578
|
+
// ── GC methods for symbols/edges ─────────────────────────────────
|
|
3579
|
+
gcOrphanSymbols() {
|
|
3580
|
+
return this.inner.gcOrphanSymbols();
|
|
3581
|
+
}
|
|
3582
|
+
gcOrphanCallEdges() {
|
|
3583
|
+
return this.inner.gcOrphanCallEdges();
|
|
3584
|
+
}
|
|
3313
3585
|
};
|
|
3314
3586
|
|
|
3315
3587
|
// src/git/index.ts
|
|
3316
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3317
|
-
import * as
|
|
3318
|
-
|
|
3588
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync, statSync } from "fs";
|
|
3589
|
+
import * as path5 from "path";
|
|
3590
|
+
function readPackedRefs(gitDir) {
|
|
3591
|
+
const packedRefsPath = path5.join(gitDir, "packed-refs");
|
|
3592
|
+
if (!existsSync3(packedRefsPath)) {
|
|
3593
|
+
return [];
|
|
3594
|
+
}
|
|
3595
|
+
try {
|
|
3596
|
+
return readFileSync4(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
3597
|
+
} catch {
|
|
3598
|
+
return [];
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
function resolveCommonGitDir(gitDir) {
|
|
3602
|
+
const commonDirPath = path5.join(gitDir, "commondir");
|
|
3603
|
+
if (!existsSync3(commonDirPath)) {
|
|
3604
|
+
return gitDir;
|
|
3605
|
+
}
|
|
3606
|
+
try {
|
|
3607
|
+
const raw = readFileSync4(commonDirPath, "utf-8").trim();
|
|
3608
|
+
if (!raw) {
|
|
3609
|
+
return gitDir;
|
|
3610
|
+
}
|
|
3611
|
+
const resolved = path5.isAbsolute(raw) ? raw : path5.resolve(gitDir, raw);
|
|
3612
|
+
if (existsSync3(resolved)) {
|
|
3613
|
+
return resolved;
|
|
3614
|
+
}
|
|
3615
|
+
} catch {
|
|
3616
|
+
return gitDir;
|
|
3617
|
+
}
|
|
3618
|
+
return gitDir;
|
|
3619
|
+
}
|
|
3319
3620
|
function resolveGitDir(repoRoot) {
|
|
3320
|
-
const gitPath =
|
|
3621
|
+
const gitPath = path5.join(repoRoot, ".git");
|
|
3321
3622
|
if (!existsSync3(gitPath)) {
|
|
3322
3623
|
return null;
|
|
3323
3624
|
}
|
|
@@ -3327,11 +3628,11 @@ function resolveGitDir(repoRoot) {
|
|
|
3327
3628
|
return gitPath;
|
|
3328
3629
|
}
|
|
3329
3630
|
if (stat.isFile()) {
|
|
3330
|
-
const content =
|
|
3631
|
+
const content = readFileSync4(gitPath, "utf-8").trim();
|
|
3331
3632
|
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
3332
3633
|
if (match) {
|
|
3333
3634
|
const gitdir = match[1];
|
|
3334
|
-
const resolvedPath =
|
|
3635
|
+
const resolvedPath = path5.isAbsolute(gitdir) ? gitdir : path5.resolve(repoRoot, gitdir);
|
|
3335
3636
|
if (existsSync3(resolvedPath)) {
|
|
3336
3637
|
return resolvedPath;
|
|
3337
3638
|
}
|
|
@@ -3349,12 +3650,12 @@ function getCurrentBranch(repoRoot) {
|
|
|
3349
3650
|
if (!gitDir) {
|
|
3350
3651
|
return null;
|
|
3351
3652
|
}
|
|
3352
|
-
const headPath =
|
|
3653
|
+
const headPath = path5.join(gitDir, "HEAD");
|
|
3353
3654
|
if (!existsSync3(headPath)) {
|
|
3354
3655
|
return null;
|
|
3355
3656
|
}
|
|
3356
3657
|
try {
|
|
3357
|
-
const headContent =
|
|
3658
|
+
const headContent = readFileSync4(headPath, "utf-8").trim();
|
|
3358
3659
|
const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
|
|
3359
3660
|
if (match) {
|
|
3360
3661
|
return match[1];
|
|
@@ -3369,37 +3670,20 @@ function getCurrentBranch(repoRoot) {
|
|
|
3369
3670
|
}
|
|
3370
3671
|
function getBaseBranch(repoRoot) {
|
|
3371
3672
|
const gitDir = resolveGitDir(repoRoot);
|
|
3673
|
+
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
3372
3674
|
const candidates = ["main", "master", "develop", "trunk"];
|
|
3373
|
-
if (
|
|
3675
|
+
if (refStoreDir) {
|
|
3374
3676
|
for (const candidate of candidates) {
|
|
3375
|
-
const refPath =
|
|
3677
|
+
const refPath = path5.join(refStoreDir, "refs", "heads", candidate);
|
|
3376
3678
|
if (existsSync3(refPath)) {
|
|
3377
3679
|
return candidate;
|
|
3378
3680
|
}
|
|
3379
|
-
const
|
|
3380
|
-
if (
|
|
3381
|
-
|
|
3382
|
-
const content = readFileSync3(packedRefsPath, "utf-8");
|
|
3383
|
-
if (content.includes(`refs/heads/${candidate}`)) {
|
|
3384
|
-
return candidate;
|
|
3385
|
-
}
|
|
3386
|
-
} catch {
|
|
3387
|
-
}
|
|
3681
|
+
const packedRefs = readPackedRefs(refStoreDir);
|
|
3682
|
+
if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
|
|
3683
|
+
return candidate;
|
|
3388
3684
|
}
|
|
3389
3685
|
}
|
|
3390
3686
|
}
|
|
3391
|
-
try {
|
|
3392
|
-
const result = execSync("git remote show origin", {
|
|
3393
|
-
cwd: repoRoot,
|
|
3394
|
-
encoding: "utf-8",
|
|
3395
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3396
|
-
});
|
|
3397
|
-
const match = result.match(/HEAD branch: (.+)/);
|
|
3398
|
-
if (match) {
|
|
3399
|
-
return match[1].trim();
|
|
3400
|
-
}
|
|
3401
|
-
} catch {
|
|
3402
|
-
}
|
|
3403
3687
|
return getCurrentBranch(repoRoot) ?? "main";
|
|
3404
3688
|
}
|
|
3405
3689
|
function getBranchOrDefault(repoRoot) {
|
|
@@ -3410,6 +3694,30 @@ function getBranchOrDefault(repoRoot) {
|
|
|
3410
3694
|
}
|
|
3411
3695
|
|
|
3412
3696
|
// src/indexer/index.ts
|
|
3697
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
|
|
3698
|
+
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
3699
|
+
"function_declaration",
|
|
3700
|
+
"function",
|
|
3701
|
+
"arrow_function",
|
|
3702
|
+
"method_definition",
|
|
3703
|
+
"class_declaration",
|
|
3704
|
+
"interface_declaration",
|
|
3705
|
+
"type_alias_declaration",
|
|
3706
|
+
"enum_declaration",
|
|
3707
|
+
"function_definition",
|
|
3708
|
+
"class_definition",
|
|
3709
|
+
"decorated_definition",
|
|
3710
|
+
"method_declaration",
|
|
3711
|
+
"type_declaration",
|
|
3712
|
+
"type_spec",
|
|
3713
|
+
"function_item",
|
|
3714
|
+
"impl_item",
|
|
3715
|
+
"struct_item",
|
|
3716
|
+
"enum_item",
|
|
3717
|
+
"trait_item",
|
|
3718
|
+
"mod_item",
|
|
3719
|
+
"trait_declaration"
|
|
3720
|
+
]);
|
|
3413
3721
|
function float32ArrayToBuffer(arr) {
|
|
3414
3722
|
const float32 = new Float32Array(arr);
|
|
3415
3723
|
return Buffer.from(float32.buffer);
|
|
@@ -3434,116 +3742,1001 @@ function isRateLimitError(error) {
|
|
|
3434
3742
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
3435
3743
|
}
|
|
3436
3744
|
var INDEX_METADATA_VERSION = "1";
|
|
3437
|
-
var
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3745
|
+
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
3746
|
+
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
3747
|
+
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
3748
|
+
var rankingPathTokenCache = /* @__PURE__ */ new Map();
|
|
3749
|
+
var rankingTextTokenCache = /* @__PURE__ */ new Map();
|
|
3750
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3751
|
+
"the",
|
|
3752
|
+
"and",
|
|
3753
|
+
"for",
|
|
3754
|
+
"with",
|
|
3755
|
+
"from",
|
|
3756
|
+
"that",
|
|
3757
|
+
"this",
|
|
3758
|
+
"into",
|
|
3759
|
+
"using",
|
|
3760
|
+
"where",
|
|
3761
|
+
"what",
|
|
3762
|
+
"when",
|
|
3763
|
+
"why",
|
|
3764
|
+
"how",
|
|
3765
|
+
"are",
|
|
3766
|
+
"was",
|
|
3767
|
+
"were",
|
|
3768
|
+
"be",
|
|
3769
|
+
"been",
|
|
3770
|
+
"being",
|
|
3771
|
+
"find",
|
|
3772
|
+
"show",
|
|
3773
|
+
"get",
|
|
3774
|
+
"run",
|
|
3775
|
+
"use",
|
|
3776
|
+
"code",
|
|
3777
|
+
"function",
|
|
3778
|
+
"implementation",
|
|
3779
|
+
"retrieve",
|
|
3780
|
+
"results",
|
|
3781
|
+
"result",
|
|
3782
|
+
"search",
|
|
3783
|
+
"pipeline",
|
|
3784
|
+
"top",
|
|
3785
|
+
"in",
|
|
3786
|
+
"on",
|
|
3787
|
+
"of",
|
|
3788
|
+
"to",
|
|
3789
|
+
"by",
|
|
3790
|
+
"as",
|
|
3791
|
+
"or",
|
|
3792
|
+
"an",
|
|
3793
|
+
"a"
|
|
3794
|
+
]);
|
|
3795
|
+
var TEST_PATH_SEGMENTS = [
|
|
3796
|
+
"tests/",
|
|
3797
|
+
"__tests__/",
|
|
3798
|
+
"/test/",
|
|
3799
|
+
"fixtures/",
|
|
3800
|
+
"benchmark",
|
|
3801
|
+
"README",
|
|
3802
|
+
"ARCHITECTURE",
|
|
3803
|
+
"docs/"
|
|
3804
|
+
];
|
|
3805
|
+
var IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS = [
|
|
3806
|
+
"tests/",
|
|
3807
|
+
"__tests__/",
|
|
3808
|
+
"/test/",
|
|
3809
|
+
"fixtures/",
|
|
3810
|
+
"benchmark",
|
|
3811
|
+
"readme",
|
|
3812
|
+
"architecture",
|
|
3813
|
+
"docs/",
|
|
3814
|
+
"examples/",
|
|
3815
|
+
"example/",
|
|
3816
|
+
".github/",
|
|
3817
|
+
"/scripts/",
|
|
3818
|
+
"/migrations/",
|
|
3819
|
+
"/generated/"
|
|
3820
|
+
];
|
|
3821
|
+
var SOURCE_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3822
|
+
"implement",
|
|
3823
|
+
"implementation",
|
|
3824
|
+
"function",
|
|
3825
|
+
"method",
|
|
3826
|
+
"class",
|
|
3827
|
+
"logic",
|
|
3828
|
+
"algorithm",
|
|
3829
|
+
"pipeline",
|
|
3830
|
+
"indexer",
|
|
3831
|
+
"where"
|
|
3832
|
+
]);
|
|
3833
|
+
var DOC_TEST_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3834
|
+
"test",
|
|
3835
|
+
"tests",
|
|
3836
|
+
"fixture",
|
|
3837
|
+
"fixtures",
|
|
3838
|
+
"benchmark",
|
|
3839
|
+
"readme",
|
|
3840
|
+
"docs",
|
|
3841
|
+
"documentation"
|
|
3842
|
+
]);
|
|
3843
|
+
var DOC_INTENT_HINTS = /* @__PURE__ */ new Set([
|
|
3844
|
+
"readme",
|
|
3845
|
+
"docs",
|
|
3846
|
+
"documentation",
|
|
3847
|
+
"guide",
|
|
3848
|
+
"usage"
|
|
3849
|
+
]);
|
|
3850
|
+
function setBoundedCache(cache, key, value) {
|
|
3851
|
+
if (cache.size >= RANKING_TOKEN_CACHE_LIMIT) {
|
|
3852
|
+
const oldest = cache.keys().next().value;
|
|
3853
|
+
if (oldest !== void 0) {
|
|
3854
|
+
cache.delete(oldest);
|
|
3471
3855
|
}
|
|
3472
|
-
return path5.join(this.projectRoot, ".opencode", "index");
|
|
3473
3856
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
this.fileHashCache = new Map(Object.entries(parsed));
|
|
3480
|
-
}
|
|
3481
|
-
} catch {
|
|
3482
|
-
this.fileHashCache = /* @__PURE__ */ new Map();
|
|
3483
|
-
}
|
|
3857
|
+
cache.set(key, value);
|
|
3858
|
+
}
|
|
3859
|
+
function tokenizeTextForRanking(text) {
|
|
3860
|
+
if (!text) {
|
|
3861
|
+
return /* @__PURE__ */ new Set();
|
|
3484
3862
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
}
|
|
3490
|
-
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
3863
|
+
const lowered = text.toLowerCase();
|
|
3864
|
+
const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
|
|
3865
|
+
if (cache) {
|
|
3866
|
+
return cache;
|
|
3491
3867
|
}
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3868
|
+
const tokens = new Set(
|
|
3869
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !STOPWORDS.has(token))
|
|
3870
|
+
);
|
|
3871
|
+
setBoundedCache(rankingQueryTokenCache, lowered, tokens);
|
|
3872
|
+
setBoundedCache(rankingTextTokenCache, lowered, tokens);
|
|
3873
|
+
return tokens;
|
|
3874
|
+
}
|
|
3875
|
+
function splitPathTokens(filePath) {
|
|
3876
|
+
const lowered = filePath.toLowerCase();
|
|
3877
|
+
const cache = rankingPathTokenCache.get(lowered);
|
|
3878
|
+
if (cache) {
|
|
3879
|
+
return cache;
|
|
3880
|
+
}
|
|
3881
|
+
const normalized = lowered.replace(/[^a-z0-9/._-]/g, " ").split(/[/._-]+/).filter((token) => token.length > 1);
|
|
3882
|
+
const tokens = new Set(normalized);
|
|
3883
|
+
setBoundedCache(rankingPathTokenCache, lowered, tokens);
|
|
3884
|
+
return tokens;
|
|
3885
|
+
}
|
|
3886
|
+
function splitNameTokens(name) {
|
|
3887
|
+
if (!name) {
|
|
3888
|
+
return /* @__PURE__ */ new Set();
|
|
3496
3889
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3890
|
+
const lowered = name.toLowerCase();
|
|
3891
|
+
const cache = rankingNameTokenCache.get(lowered);
|
|
3892
|
+
if (cache) {
|
|
3893
|
+
return cache;
|
|
3499
3894
|
}
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3895
|
+
const tokens = new Set(
|
|
3896
|
+
lowered.replace(/[^\w\s]/g, " ").split(/\s+/).filter((token) => token.length > 1)
|
|
3897
|
+
);
|
|
3898
|
+
setBoundedCache(rankingNameTokenCache, lowered, tokens);
|
|
3899
|
+
return tokens;
|
|
3900
|
+
}
|
|
3901
|
+
function chunkTypeBoost(chunkType) {
|
|
3902
|
+
switch (chunkType) {
|
|
3903
|
+
case "function":
|
|
3904
|
+
case "function_declaration":
|
|
3905
|
+
case "method":
|
|
3906
|
+
case "method_definition":
|
|
3907
|
+
case "class":
|
|
3908
|
+
case "class_declaration":
|
|
3909
|
+
return 0.2;
|
|
3910
|
+
case "interface":
|
|
3911
|
+
case "type":
|
|
3912
|
+
case "enum":
|
|
3913
|
+
case "struct":
|
|
3914
|
+
case "impl":
|
|
3915
|
+
case "trait":
|
|
3916
|
+
case "module":
|
|
3917
|
+
return 0.1;
|
|
3918
|
+
default:
|
|
3919
|
+
return 0;
|
|
3506
3920
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3921
|
+
}
|
|
3922
|
+
function isTestOrDocPath(filePath) {
|
|
3923
|
+
return TEST_PATH_SEGMENTS.some((segment) => filePath.includes(segment));
|
|
3924
|
+
}
|
|
3925
|
+
function isLikelyImplementationPath(filePath) {
|
|
3926
|
+
const lowered = filePath.toLowerCase();
|
|
3927
|
+
if (IMPLEMENTATION_EXCLUDE_PATH_SEGMENTS.some((segment) => lowered.includes(segment))) {
|
|
3928
|
+
return false;
|
|
3511
3929
|
}
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
unlinkSync(this.fileHashCachePath);
|
|
3516
|
-
}
|
|
3517
|
-
await this.healthCheck();
|
|
3518
|
-
this.releaseIndexingLock();
|
|
3519
|
-
this.logger.info("Recovery complete, next index will re-process all files");
|
|
3930
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
3931
|
+
if (["md", "mdx", "txt", "rst", "adoc", "snap", "json", "yaml", "yml", "lock"].includes(ext)) {
|
|
3932
|
+
return false;
|
|
3520
3933
|
}
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3934
|
+
return true;
|
|
3935
|
+
}
|
|
3936
|
+
function classifyQueryIntent(tokens) {
|
|
3937
|
+
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
3938
|
+
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
3939
|
+
return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
|
|
3940
|
+
}
|
|
3941
|
+
function classifyQueryIntentRaw(query) {
|
|
3942
|
+
const lowerQuery = query.toLowerCase();
|
|
3943
|
+
const docTestRawHits = Array.from(DOC_TEST_INTENT_HINTS).filter(
|
|
3944
|
+
(hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)
|
|
3945
|
+
).length;
|
|
3946
|
+
const sourceRawHits = [
|
|
3947
|
+
"implement",
|
|
3948
|
+
"implementation",
|
|
3949
|
+
"implements",
|
|
3950
|
+
"function",
|
|
3951
|
+
"method",
|
|
3952
|
+
"class",
|
|
3953
|
+
"logic",
|
|
3954
|
+
"algorithm",
|
|
3955
|
+
"pipeline",
|
|
3956
|
+
"indexer"
|
|
3957
|
+
].filter((hint) => new RegExp(`\\b${hint}\\b`).test(lowerQuery)).length;
|
|
3958
|
+
if (docTestRawHits > sourceRawHits) {
|
|
3959
|
+
return "doc_test";
|
|
3960
|
+
}
|
|
3961
|
+
if (sourceRawHits > docTestRawHits) {
|
|
3962
|
+
return "source";
|
|
3963
|
+
}
|
|
3964
|
+
const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
|
|
3965
|
+
const hasIdentifierHints = extractIdentifierHints(query).length > 0;
|
|
3966
|
+
if (hasWhereIsPattern && hasIdentifierHints && docTestRawHits === 0) {
|
|
3967
|
+
return "source";
|
|
3968
|
+
}
|
|
3969
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
3970
|
+
return classifyQueryIntent(queryTokens);
|
|
3971
|
+
}
|
|
3972
|
+
function classifyDocIntent(tokens) {
|
|
3973
|
+
const docHits = tokens.filter((t) => DOC_INTENT_HINTS.has(t)).length;
|
|
3974
|
+
const testHits = tokens.filter((t) => ["test", "tests", "fixture", "fixtures", "benchmark"].includes(t)).length;
|
|
3975
|
+
if (docHits > 0 && testHits === 0) return "docs";
|
|
3976
|
+
if (testHits > 0 && docHits === 0) return "test";
|
|
3977
|
+
if (testHits > 0 || docHits > 0) return "mixed";
|
|
3978
|
+
return "none";
|
|
3979
|
+
}
|
|
3980
|
+
function isImplementationChunkType(chunkType) {
|
|
3981
|
+
return [
|
|
3982
|
+
"export_statement",
|
|
3983
|
+
"function",
|
|
3984
|
+
"function_declaration",
|
|
3985
|
+
"method",
|
|
3986
|
+
"method_definition",
|
|
3987
|
+
"class",
|
|
3988
|
+
"class_declaration",
|
|
3989
|
+
"interface",
|
|
3990
|
+
"type",
|
|
3991
|
+
"enum",
|
|
3992
|
+
"module"
|
|
3993
|
+
].includes(chunkType);
|
|
3994
|
+
}
|
|
3995
|
+
function extractIdentifierHints(query) {
|
|
3996
|
+
const identifiers = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
3997
|
+
return identifiers.filter((id) => id.length >= 3).filter((id) => {
|
|
3998
|
+
const lower = id.toLowerCase();
|
|
3999
|
+
if (STOPWORDS.has(lower)) return false;
|
|
4000
|
+
return /[A-Z]/.test(id) || id.includes("_") || id.endsWith("Results") || id.endsWith("Result");
|
|
4001
|
+
}).map((id) => id.toLowerCase());
|
|
4002
|
+
}
|
|
4003
|
+
function extractCodeTermHints(query) {
|
|
4004
|
+
const terms = query.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
4005
|
+
return terms.map((term) => term.toLowerCase()).filter((term) => term.length >= 3).filter((term) => !STOPWORDS.has(term));
|
|
4006
|
+
}
|
|
4007
|
+
function normalizeIdentifierVariants(identifier) {
|
|
4008
|
+
const lower = identifier.toLowerCase();
|
|
4009
|
+
const compact = lower.replace(/[^a-z0-9]/g, "");
|
|
4010
|
+
const snake = compact.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
4011
|
+
const kebab = snake.replace(/_/g, "-");
|
|
4012
|
+
const variants = [lower, compact, snake, kebab].filter((value) => value.length > 0);
|
|
4013
|
+
return Array.from(new Set(variants));
|
|
4014
|
+
}
|
|
4015
|
+
function scoreIdentifierMatch(name, filePath, hints) {
|
|
4016
|
+
const nameLower = (name ?? "").toLowerCase();
|
|
4017
|
+
const pathLower = filePath.toLowerCase();
|
|
4018
|
+
let best = 0;
|
|
4019
|
+
for (const hint of hints) {
|
|
4020
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
4021
|
+
for (const variant of variants) {
|
|
4022
|
+
if (nameLower === variant) {
|
|
4023
|
+
best = Math.max(best, 1);
|
|
4024
|
+
} else if (nameLower.includes(variant)) {
|
|
4025
|
+
best = Math.max(best, 0.8);
|
|
4026
|
+
} else if (pathLower.includes(variant)) {
|
|
4027
|
+
best = Math.max(best, 0.6);
|
|
3526
4028
|
}
|
|
3527
|
-
} catch {
|
|
3528
|
-
return [];
|
|
3529
4029
|
}
|
|
4030
|
+
}
|
|
4031
|
+
return best;
|
|
4032
|
+
}
|
|
4033
|
+
function extractPrimaryIdentifierQueryHint(query) {
|
|
4034
|
+
const identifiers = extractIdentifierHints(query);
|
|
4035
|
+
if (identifiers.length > 0) {
|
|
4036
|
+
return identifiers[0] ?? null;
|
|
4037
|
+
}
|
|
4038
|
+
const codeTerms = extractCodeTermHints(query);
|
|
4039
|
+
const best = codeTerms.find((term) => term.length >= 6);
|
|
4040
|
+
return best ?? null;
|
|
4041
|
+
}
|
|
4042
|
+
var FILE_PATH_HINT_EXTENSIONS = [
|
|
4043
|
+
"ts",
|
|
4044
|
+
"tsx",
|
|
4045
|
+
"js",
|
|
4046
|
+
"jsx",
|
|
4047
|
+
"mjs",
|
|
4048
|
+
"cjs",
|
|
4049
|
+
"mts",
|
|
4050
|
+
"cts",
|
|
4051
|
+
"py",
|
|
4052
|
+
"rs",
|
|
4053
|
+
"go",
|
|
4054
|
+
"java",
|
|
4055
|
+
"kt",
|
|
4056
|
+
"kts",
|
|
4057
|
+
"swift",
|
|
4058
|
+
"rb",
|
|
4059
|
+
"php",
|
|
4060
|
+
"c",
|
|
4061
|
+
"h",
|
|
4062
|
+
"cc",
|
|
4063
|
+
"cpp",
|
|
4064
|
+
"cxx",
|
|
4065
|
+
"hpp",
|
|
4066
|
+
"cs",
|
|
4067
|
+
"scala",
|
|
4068
|
+
"lua",
|
|
4069
|
+
"sh",
|
|
4070
|
+
"bash",
|
|
4071
|
+
"zsh",
|
|
4072
|
+
"json",
|
|
4073
|
+
"yaml",
|
|
4074
|
+
"yml",
|
|
4075
|
+
"toml"
|
|
4076
|
+
];
|
|
4077
|
+
var FILE_PATH_HINT_SUFFIX_REGEX = new RegExp(
|
|
4078
|
+
"\\s+\\bin\\s+[\"'`]?((?:\\.\\/)?(?:[A-Za-z0-9._-]+\\/)+[A-Za-z0-9._-]+\\.(?:" + FILE_PATH_HINT_EXTENSIONS.join("|") + "))[\"'`]?[\\])}>.,;!?]*\\s*$",
|
|
4079
|
+
"i"
|
|
4080
|
+
);
|
|
4081
|
+
function normalizeFilePathForHintMatch(filePath) {
|
|
4082
|
+
return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
4083
|
+
}
|
|
4084
|
+
function pathMatchesHint(filePath, hint) {
|
|
4085
|
+
const normalizedPath = normalizeFilePathForHintMatch(filePath);
|
|
4086
|
+
const normalizedHint = normalizeFilePathForHintMatch(hint);
|
|
4087
|
+
return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
|
|
4088
|
+
}
|
|
4089
|
+
function extractFilePathHint(query) {
|
|
4090
|
+
const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
|
|
4091
|
+
const rawPath = match?.[1];
|
|
4092
|
+
if (!rawPath) {
|
|
4093
|
+
return null;
|
|
4094
|
+
}
|
|
4095
|
+
return rawPath.replace(/^\.\//, "");
|
|
4096
|
+
}
|
|
4097
|
+
function stripFilePathHint(query) {
|
|
4098
|
+
const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
|
|
4099
|
+
return stripped.length > 0 ? stripped : query;
|
|
4100
|
+
}
|
|
4101
|
+
function buildDeterministicIdentifierPass(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4102
|
+
if (!prioritizeSourcePaths) {
|
|
3530
4103
|
return [];
|
|
3531
4104
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
4105
|
+
const primary = extractPrimaryIdentifierQueryHint(query);
|
|
4106
|
+
if (!primary) {
|
|
4107
|
+
return [];
|
|
4108
|
+
}
|
|
4109
|
+
const filePathHint = extractFilePathHint(query);
|
|
4110
|
+
const primaryVariants = normalizeIdentifierVariants(primary);
|
|
4111
|
+
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);
|
|
4112
|
+
const deterministic = candidates.filter(
|
|
4113
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
4114
|
+
).map((candidate) => {
|
|
4115
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4116
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
4117
|
+
let maxMatch = 0;
|
|
4118
|
+
const nameMatchesPrimary = primaryVariants.some(
|
|
4119
|
+
(variant) => nameLower === variant || nameLower.replace(/[^a-z0-9]/g, "") === variant.replace(/[^a-z0-9]/g, "")
|
|
4120
|
+
);
|
|
4121
|
+
const pathMatchesFileHint = filePathHint ? pathMatchesHint(candidate.metadata.filePath, filePathHint) : false;
|
|
4122
|
+
for (const hint of hints) {
|
|
4123
|
+
const variants = normalizeIdentifierVariants(hint);
|
|
4124
|
+
for (const variant of variants) {
|
|
4125
|
+
if (nameLower === variant) {
|
|
4126
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
4127
|
+
} else if (nameLower.includes(variant)) {
|
|
4128
|
+
maxMatch = Math.max(maxMatch, 0.85);
|
|
4129
|
+
} else if (pathLower.includes(variant)) {
|
|
4130
|
+
maxMatch = Math.max(maxMatch, 0.7);
|
|
4131
|
+
}
|
|
3537
4132
|
}
|
|
3538
|
-
return;
|
|
3539
4133
|
}
|
|
3540
|
-
|
|
4134
|
+
if (pathMatchesFileHint && nameMatchesPrimary) {
|
|
4135
|
+
maxMatch = Math.max(maxMatch, 1);
|
|
4136
|
+
}
|
|
4137
|
+
return {
|
|
4138
|
+
candidate,
|
|
4139
|
+
maxMatch,
|
|
4140
|
+
pathMatchesFileHint,
|
|
4141
|
+
nameMatchesPrimary
|
|
4142
|
+
};
|
|
4143
|
+
}).filter((entry) => entry.maxMatch >= 0.7).sort((a, b) => {
|
|
4144
|
+
const aAnchored = a.pathMatchesFileHint && a.nameMatchesPrimary ? 1 : 0;
|
|
4145
|
+
const bAnchored = b.pathMatchesFileHint && b.nameMatchesPrimary ? 1 : 0;
|
|
4146
|
+
if (aAnchored !== bAnchored) return bAnchored - aAnchored;
|
|
4147
|
+
if (b.maxMatch !== a.maxMatch) return b.maxMatch - a.maxMatch;
|
|
4148
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4149
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4150
|
+
}).slice(0, Math.max(limit * 2, 12));
|
|
4151
|
+
return deterministic.map((entry) => ({
|
|
4152
|
+
id: entry.candidate.id,
|
|
4153
|
+
score: entry.pathMatchesFileHint && entry.nameMatchesPrimary ? 0.995 : Math.min(1, 0.9 + entry.maxMatch * 0.09),
|
|
4154
|
+
metadata: entry.candidate.metadata
|
|
4155
|
+
}));
|
|
4156
|
+
}
|
|
4157
|
+
function fuseResultsWeighted(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4158
|
+
const semanticWeight = 1 - keywordWeight;
|
|
4159
|
+
const fusedScores = /* @__PURE__ */ new Map();
|
|
4160
|
+
for (const r of semanticResults) {
|
|
4161
|
+
fusedScores.set(r.id, {
|
|
4162
|
+
score: r.score * semanticWeight,
|
|
4163
|
+
metadata: r.metadata
|
|
4164
|
+
});
|
|
3541
4165
|
}
|
|
3542
|
-
|
|
3543
|
-
const existing =
|
|
3544
|
-
existing
|
|
3545
|
-
|
|
3546
|
-
|
|
4166
|
+
for (const r of keywordResults) {
|
|
4167
|
+
const existing = fusedScores.get(r.id);
|
|
4168
|
+
if (existing) {
|
|
4169
|
+
existing.score += r.score * keywordWeight;
|
|
4170
|
+
} else {
|
|
4171
|
+
fusedScores.set(r.id, {
|
|
4172
|
+
score: r.score * keywordWeight,
|
|
4173
|
+
metadata: r.metadata
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4178
|
+
id,
|
|
4179
|
+
score: data.score,
|
|
4180
|
+
metadata: data.metadata
|
|
4181
|
+
}));
|
|
4182
|
+
results.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4183
|
+
return results.slice(0, limit);
|
|
4184
|
+
}
|
|
4185
|
+
function fuseResultsRrf(semanticResults, keywordResults, rrfK, limit) {
|
|
4186
|
+
const maxPossibleRaw = 2 / (rrfK + 1);
|
|
4187
|
+
const rankByIdSemantic = /* @__PURE__ */ new Map();
|
|
4188
|
+
const rankByIdKeyword = /* @__PURE__ */ new Map();
|
|
4189
|
+
const metadataById = /* @__PURE__ */ new Map();
|
|
4190
|
+
semanticResults.forEach((result, index) => {
|
|
4191
|
+
rankByIdSemantic.set(result.id, index + 1);
|
|
4192
|
+
metadataById.set(result.id, result.metadata);
|
|
4193
|
+
});
|
|
4194
|
+
keywordResults.forEach((result, index) => {
|
|
4195
|
+
rankByIdKeyword.set(result.id, index + 1);
|
|
4196
|
+
if (!metadataById.has(result.id)) {
|
|
4197
|
+
metadataById.set(result.id, result.metadata);
|
|
4198
|
+
}
|
|
4199
|
+
});
|
|
4200
|
+
const allIds = /* @__PURE__ */ new Set([...rankByIdSemantic.keys(), ...rankByIdKeyword.keys()]);
|
|
4201
|
+
const fused = [];
|
|
4202
|
+
for (const id of allIds) {
|
|
4203
|
+
const semanticRank = rankByIdSemantic.get(id);
|
|
4204
|
+
const keywordRank = rankByIdKeyword.get(id);
|
|
4205
|
+
const semanticScore = semanticRank ? 1 / (rrfK + semanticRank) : 0;
|
|
4206
|
+
const keywordScore = keywordRank ? 1 / (rrfK + keywordRank) : 0;
|
|
4207
|
+
const metadata = metadataById.get(id);
|
|
4208
|
+
if (!metadata) continue;
|
|
4209
|
+
fused.push({
|
|
4210
|
+
id,
|
|
4211
|
+
score: maxPossibleRaw > 0 ? (semanticScore + keywordScore) / maxPossibleRaw : 0,
|
|
4212
|
+
metadata
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
4215
|
+
fused.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4216
|
+
return fused.slice(0, limit);
|
|
4217
|
+
}
|
|
4218
|
+
function rerankResults(query, candidates, rerankTopN, options) {
|
|
4219
|
+
if (rerankTopN <= 0 || candidates.length <= 1) {
|
|
4220
|
+
return candidates;
|
|
4221
|
+
}
|
|
4222
|
+
const topN = Math.min(rerankTopN, candidates.length);
|
|
4223
|
+
const queryTokens = tokenizeTextForRanking(query);
|
|
4224
|
+
if (queryTokens.size === 0) {
|
|
4225
|
+
return candidates;
|
|
4226
|
+
}
|
|
4227
|
+
const queryTokenList = Array.from(queryTokens);
|
|
4228
|
+
const intent = classifyQueryIntentRaw(query);
|
|
4229
|
+
const docIntent = classifyDocIntent(queryTokenList);
|
|
4230
|
+
const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
|
|
4231
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4232
|
+
const head = candidates.slice(0, topN).map((candidate, idx) => {
|
|
4233
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4234
|
+
const nameTokens = splitNameTokens(candidate.metadata.name ?? "");
|
|
4235
|
+
const chunkTypeTokens = tokenizeTextForRanking(candidate.metadata.chunkType);
|
|
4236
|
+
let exactOrPrefixNameHits = 0;
|
|
4237
|
+
let pathOverlap = 0;
|
|
4238
|
+
let chunkTypeHits = 0;
|
|
4239
|
+
for (const token of queryTokenList) {
|
|
4240
|
+
if (nameTokens.has(token)) {
|
|
4241
|
+
exactOrPrefixNameHits += 1;
|
|
4242
|
+
} else {
|
|
4243
|
+
for (const nameToken of nameTokens) {
|
|
4244
|
+
if (nameToken.startsWith(token) || token.startsWith(nameToken)) {
|
|
4245
|
+
exactOrPrefixNameHits += 1;
|
|
4246
|
+
break;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
if (pathTokens.has(token)) {
|
|
4251
|
+
pathOverlap += 1;
|
|
4252
|
+
}
|
|
4253
|
+
if (chunkTypeTokens.has(token)) {
|
|
4254
|
+
chunkTypeHits += 1;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
const likelyTestOrDoc = isTestOrDocPath(candidate.metadata.filePath);
|
|
4258
|
+
const lowerPath = candidate.metadata.filePath.toLowerCase();
|
|
4259
|
+
const lowerName = (candidate.metadata.name ?? "").toLowerCase();
|
|
4260
|
+
const hasIdentifierMatch = identifierHints.some((id) => lowerPath.includes(id) || lowerName.includes(id));
|
|
4261
|
+
const implementationPathBoost = preferSourcePaths && isLikelyImplementationPath(candidate.metadata.filePath) ? 0.08 : 0;
|
|
4262
|
+
const isReadmePath = candidate.metadata.filePath.toLowerCase().includes("readme");
|
|
4263
|
+
const testDocPenalty = preferSourcePaths && likelyTestOrDoc ? 0.12 : 0;
|
|
4264
|
+
const readmeDocBoost = !preferSourcePaths && isReadmePath ? 0.08 : 0;
|
|
4265
|
+
const identifierBoost = hasIdentifierMatch ? 0.12 : 0;
|
|
4266
|
+
const tokenCoverage = queryTokenList.length > 0 ? (exactOrPrefixNameHits + pathOverlap + chunkTypeHits) / queryTokenList.length : 0;
|
|
4267
|
+
const coverageBoost = Math.min(0.12, tokenCoverage * 0.06);
|
|
4268
|
+
const deterministicBoost = exactOrPrefixNameHits * 0.08 + pathOverlap * 0.03 + chunkTypeHits * 0.02 + coverageBoost + identifierBoost + implementationPathBoost - testDocPenalty + readmeDocBoost + chunkTypeBoost(candidate.metadata.chunkType);
|
|
4269
|
+
return {
|
|
4270
|
+
candidate,
|
|
4271
|
+
boostedScore: candidate.score + deterministicBoost,
|
|
4272
|
+
originalIndex: idx,
|
|
4273
|
+
hasIdentifierMatch,
|
|
4274
|
+
implementationChunk: isImplementationChunkType(candidate.metadata.chunkType),
|
|
4275
|
+
isLikelyImplementationPath: isLikelyImplementationPath(candidate.metadata.filePath),
|
|
4276
|
+
isTestOrDocPath: likelyTestOrDoc,
|
|
4277
|
+
isReadmePath
|
|
4278
|
+
};
|
|
4279
|
+
});
|
|
4280
|
+
head.sort((a, b) => {
|
|
4281
|
+
if (b.boostedScore !== a.boostedScore) return b.boostedScore - a.boostedScore;
|
|
4282
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4283
|
+
if (a.originalIndex !== b.originalIndex) return a.originalIndex - b.originalIndex;
|
|
4284
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4285
|
+
});
|
|
4286
|
+
if (preferSourcePaths) {
|
|
4287
|
+
head.sort((a, b) => {
|
|
4288
|
+
const aId = a.hasIdentifierMatch ? 1 : 0;
|
|
4289
|
+
const bId = b.hasIdentifierMatch ? 1 : 0;
|
|
4290
|
+
if (aId !== bId) return bId - aId;
|
|
4291
|
+
const aImpl = a.implementationChunk ? 1 : 0;
|
|
4292
|
+
const bImpl = b.implementationChunk ? 1 : 0;
|
|
4293
|
+
if (aImpl !== bImpl) return bImpl - aImpl;
|
|
4294
|
+
const aImplementationPath = a.isLikelyImplementationPath ? 1 : 0;
|
|
4295
|
+
const bImplementationPath = b.isLikelyImplementationPath ? 1 : 0;
|
|
4296
|
+
if (aImplementationPath !== bImplementationPath) return bImplementationPath - aImplementationPath;
|
|
4297
|
+
const aTestDoc = a.isTestOrDocPath ? 1 : 0;
|
|
4298
|
+
const bTestDoc = b.isTestOrDocPath ? 1 : 0;
|
|
4299
|
+
if (aTestDoc !== bTestDoc) return aTestDoc - bTestDoc;
|
|
4300
|
+
return 0;
|
|
4301
|
+
});
|
|
4302
|
+
} else if (docIntent === "docs") {
|
|
4303
|
+
head.sort((a, b) => {
|
|
4304
|
+
const aReadme = a.isReadmePath ? 1 : 0;
|
|
4305
|
+
const bReadme = b.isReadmePath ? 1 : 0;
|
|
4306
|
+
if (aReadme !== bReadme) return bReadme - aReadme;
|
|
4307
|
+
return 0;
|
|
4308
|
+
});
|
|
4309
|
+
}
|
|
4310
|
+
const tail = candidates.slice(topN);
|
|
4311
|
+
return [...head.map((entry) => entry.candidate), ...tail];
|
|
4312
|
+
}
|
|
4313
|
+
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4314
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4315
|
+
const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
|
|
4316
|
+
const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
|
|
4317
|
+
const rerankPool = fused.slice(0, rerankPoolLimit);
|
|
4318
|
+
return rerankResults(query, rerankPool, options.rerankTopN, {
|
|
4319
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
|
|
4320
|
+
});
|
|
4321
|
+
}
|
|
4322
|
+
function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
4323
|
+
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
4324
|
+
const bounded = semanticResults.slice(0, overfetchLimit);
|
|
4325
|
+
return rerankResults(query, bounded, options.rerankTopN, {
|
|
4326
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
|
|
4327
|
+
});
|
|
4328
|
+
}
|
|
4329
|
+
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4330
|
+
if (combined.length === 0) {
|
|
4331
|
+
return combined;
|
|
4332
|
+
}
|
|
4333
|
+
if (!prioritizeSourcePaths) {
|
|
4334
|
+
return combined;
|
|
4335
|
+
}
|
|
4336
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4337
|
+
if (identifierHints.length === 0) {
|
|
4338
|
+
return combined;
|
|
4339
|
+
}
|
|
4340
|
+
const combinedById = new Map(combined.map((candidate) => [candidate.id, candidate]));
|
|
4341
|
+
const candidateUnion = /* @__PURE__ */ new Map();
|
|
4342
|
+
for (const candidate of semanticCandidates) {
|
|
4343
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4344
|
+
}
|
|
4345
|
+
for (const candidate of keywordCandidates) {
|
|
4346
|
+
if (!candidateUnion.has(candidate.id)) {
|
|
4347
|
+
candidateUnion.set(candidate.id, candidate);
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
if (database) {
|
|
4351
|
+
for (const identifier of identifierHints) {
|
|
4352
|
+
const symbols = database.getSymbolsByName(identifier);
|
|
4353
|
+
for (const symbol of symbols) {
|
|
4354
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4355
|
+
for (const chunk of chunks) {
|
|
4356
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4357
|
+
continue;
|
|
4358
|
+
}
|
|
4359
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4360
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4361
|
+
continue;
|
|
4362
|
+
}
|
|
4363
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4364
|
+
continue;
|
|
4365
|
+
}
|
|
4366
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4367
|
+
continue;
|
|
4368
|
+
}
|
|
4369
|
+
const existing = combinedById.get(chunk.chunkId) ?? candidateUnion.get(chunk.chunkId);
|
|
4370
|
+
const metadata = existing?.metadata ?? {
|
|
4371
|
+
filePath: chunk.filePath,
|
|
4372
|
+
startLine: chunk.startLine,
|
|
4373
|
+
endLine: chunk.endLine,
|
|
4374
|
+
chunkType,
|
|
4375
|
+
name: chunk.name ?? void 0,
|
|
4376
|
+
language: chunk.language,
|
|
4377
|
+
hash: chunk.contentHash
|
|
4378
|
+
};
|
|
4379
|
+
const baselineScore = existing?.score ?? 0.5;
|
|
4380
|
+
candidateUnion.set(chunk.chunkId, {
|
|
4381
|
+
id: chunk.chunkId,
|
|
4382
|
+
score: Math.min(1, baselineScore + 0.5),
|
|
4383
|
+
metadata
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
const promoted = [];
|
|
4390
|
+
for (const candidate of candidateUnion.values()) {
|
|
4391
|
+
const filePathLower = candidate.metadata.filePath.toLowerCase();
|
|
4392
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4393
|
+
const exactIdentifierMatch = identifierHints.some((hint) => nameLower === hint);
|
|
4394
|
+
const hasIdentifierMatch = exactIdentifierMatch || identifierHints.some(
|
|
4395
|
+
(hint) => nameLower.includes(hint) || filePathLower.includes(hint)
|
|
4396
|
+
);
|
|
4397
|
+
if (!hasIdentifierMatch) {
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
if (!isImplementationChunkType(candidate.metadata.chunkType)) {
|
|
4401
|
+
continue;
|
|
4402
|
+
}
|
|
4403
|
+
if (!isLikelyImplementationPath(candidate.metadata.filePath)) {
|
|
4404
|
+
continue;
|
|
4405
|
+
}
|
|
4406
|
+
const existing = combinedById.get(candidate.id) ?? candidate;
|
|
4407
|
+
const rescueBoost = exactIdentifierMatch ? 0.45 : 0.25;
|
|
4408
|
+
const boostedScore = Math.min(1, Math.max(existing.score, candidate.score) + rescueBoost);
|
|
4409
|
+
promoted.push({
|
|
4410
|
+
id: existing.id,
|
|
4411
|
+
score: boostedScore,
|
|
4412
|
+
metadata: existing.metadata
|
|
4413
|
+
});
|
|
4414
|
+
}
|
|
4415
|
+
if (promoted.length === 0) {
|
|
4416
|
+
return combined;
|
|
4417
|
+
}
|
|
4418
|
+
promoted.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4419
|
+
const promotedIds = new Set(promoted.map((candidate) => candidate.id));
|
|
4420
|
+
const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
|
|
4421
|
+
return [...promoted, ...remainder];
|
|
4422
|
+
}
|
|
4423
|
+
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4424
|
+
if (!prioritizeSourcePaths) {
|
|
4425
|
+
return [];
|
|
4426
|
+
}
|
|
4427
|
+
const identifierHints = extractIdentifierHints(query);
|
|
4428
|
+
const codeTermHints = extractCodeTermHints(query);
|
|
4429
|
+
if (identifierHints.length === 0 && codeTermHints.length === 0) {
|
|
4430
|
+
return [];
|
|
4431
|
+
}
|
|
4432
|
+
const symbolCandidates = /* @__PURE__ */ new Map();
|
|
4433
|
+
const filePathHint = extractFilePathHint(query);
|
|
4434
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4435
|
+
const upsertChunkCandidate = (chunk, identifier, normalizedIdentifier, baseScore) => {
|
|
4436
|
+
if (branchChunkIds && !branchChunkIds.has(chunk.chunkId)) {
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
const chunkType = chunk.nodeType ?? "other";
|
|
4440
|
+
if (!isImplementationChunkType(chunkType)) {
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
if (!isLikelyImplementationPath(chunk.filePath)) {
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
const nameLower = (chunk.name ?? "").toLowerCase();
|
|
4447
|
+
const exactName = nameLower === identifier || nameLower.replace(/_/g, "") === normalizedIdentifier;
|
|
4448
|
+
const base = baseScore ?? (exactName ? 0.99 : 0.88);
|
|
4449
|
+
const existing = symbolCandidates.get(chunk.chunkId);
|
|
4450
|
+
if (!existing || base > existing.score) {
|
|
4451
|
+
symbolCandidates.set(chunk.chunkId, {
|
|
4452
|
+
id: chunk.chunkId,
|
|
4453
|
+
score: base,
|
|
4454
|
+
metadata: {
|
|
4455
|
+
filePath: chunk.filePath,
|
|
4456
|
+
startLine: chunk.startLine,
|
|
4457
|
+
endLine: chunk.endLine,
|
|
4458
|
+
chunkType,
|
|
4459
|
+
name: chunk.name ?? void 0,
|
|
4460
|
+
language: chunk.language,
|
|
4461
|
+
hash: chunk.contentHash
|
|
4462
|
+
}
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
};
|
|
4466
|
+
const normalizedHints = identifierHints.flatMap((hint) => [
|
|
4467
|
+
hint,
|
|
4468
|
+
hint.replace(/_/g, ""),
|
|
4469
|
+
hint.replace(/_/g, "-")
|
|
4470
|
+
]).filter((hint, idx, arr) => hint.length >= 3 && arr.indexOf(hint) === idx).slice(0, 6);
|
|
4471
|
+
for (const identifier of normalizedHints) {
|
|
4472
|
+
const symbols = [
|
|
4473
|
+
...database.getSymbolsByName(identifier),
|
|
4474
|
+
...database.getSymbolsByNameCi(identifier)
|
|
4475
|
+
];
|
|
4476
|
+
const chunksByName = [
|
|
4477
|
+
...database.getChunksByName(identifier),
|
|
4478
|
+
...database.getChunksByNameCi(identifier)
|
|
4479
|
+
];
|
|
4480
|
+
const normalizedIdentifier = identifier.replace(/_/g, "");
|
|
4481
|
+
const dedupSymbols = /* @__PURE__ */ new Map();
|
|
4482
|
+
for (const symbol of symbols) {
|
|
4483
|
+
dedupSymbols.set(symbol.id, symbol);
|
|
4484
|
+
}
|
|
4485
|
+
for (const symbol of dedupSymbols.values()) {
|
|
4486
|
+
const chunks = database.getChunksByFile(symbol.filePath);
|
|
4487
|
+
for (const chunk of chunks) {
|
|
4488
|
+
if (chunk.startLine > symbol.startLine || chunk.endLine < symbol.endLine) {
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
const dedupChunksByName = /* @__PURE__ */ new Map();
|
|
4495
|
+
for (const chunk of chunksByName) {
|
|
4496
|
+
dedupChunksByName.set(chunk.chunkId, chunk);
|
|
4497
|
+
}
|
|
4498
|
+
for (const chunk of dedupChunksByName.values()) {
|
|
4499
|
+
upsertChunkCandidate(chunk, identifier, normalizedIdentifier);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
if (filePathHint && primaryHint) {
|
|
4503
|
+
const primaryChunks = [
|
|
4504
|
+
...database.getChunksByName(primaryHint),
|
|
4505
|
+
...database.getChunksByNameCi(primaryHint)
|
|
4506
|
+
];
|
|
4507
|
+
const dedupPrimaryChunks = /* @__PURE__ */ new Map();
|
|
4508
|
+
for (const chunk of primaryChunks) {
|
|
4509
|
+
dedupPrimaryChunks.set(chunk.chunkId, chunk);
|
|
4510
|
+
}
|
|
4511
|
+
for (const chunk of dedupPrimaryChunks.values()) {
|
|
4512
|
+
if (!pathMatchesHint(chunk.filePath, filePathHint)) {
|
|
4513
|
+
continue;
|
|
4514
|
+
}
|
|
4515
|
+
const normalizedPrimary = primaryHint.replace(/_/g, "");
|
|
4516
|
+
upsertChunkCandidate(chunk, primaryHint, normalizedPrimary, 1);
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4520
|
+
if (ranked.length === 0) {
|
|
4521
|
+
const implementationFallback = fallbackCandidates.filter(
|
|
4522
|
+
(candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath(candidate.metadata.filePath)
|
|
4523
|
+
);
|
|
4524
|
+
for (const candidate of implementationFallback) {
|
|
4525
|
+
const nameLower = (candidate.metadata.name ?? "").toLowerCase();
|
|
4526
|
+
const pathLower = candidate.metadata.filePath.toLowerCase();
|
|
4527
|
+
const exactHintMatch = normalizedHints.some((hint) => nameLower === hint || nameLower.replace(/_/g, "") === hint.replace(/_/g, ""));
|
|
4528
|
+
const tokenizedName = tokenizeTextForRanking(nameLower);
|
|
4529
|
+
const tokenHits = codeTermHints.filter((term) => tokenizedName.has(term) || pathLower.includes(term)).length;
|
|
4530
|
+
if (!exactHintMatch && tokenHits === 0) {
|
|
4531
|
+
continue;
|
|
4532
|
+
}
|
|
4533
|
+
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));
|
|
4534
|
+
symbolCandidates.set(candidate.id, {
|
|
4535
|
+
id: candidate.id,
|
|
4536
|
+
score: laneScore,
|
|
4537
|
+
metadata: candidate.metadata
|
|
4538
|
+
});
|
|
4539
|
+
}
|
|
4540
|
+
if (symbolCandidates.size === 0) {
|
|
4541
|
+
const queryTokenSet = tokenizeTextForRanking(query);
|
|
4542
|
+
const rankedFallback = implementationFallback.map((candidate) => {
|
|
4543
|
+
const nameTokens = tokenizeTextForRanking(candidate.metadata.name ?? "");
|
|
4544
|
+
const pathTokens = splitPathTokens(candidate.metadata.filePath);
|
|
4545
|
+
let overlap = 0;
|
|
4546
|
+
for (const token of queryTokenSet) {
|
|
4547
|
+
if (nameTokens.has(token) || pathTokens.has(token)) {
|
|
4548
|
+
overlap += 1;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
const overlapScore = queryTokenSet.size > 0 ? overlap / queryTokenSet.size : 0;
|
|
4552
|
+
return {
|
|
4553
|
+
candidate,
|
|
4554
|
+
overlapScore
|
|
4555
|
+
};
|
|
4556
|
+
}).filter((entry) => entry.overlapScore > 0).sort((a, b) => b.overlapScore - a.overlapScore || b.candidate.score - a.candidate.score).slice(0, Math.max(limit, 3));
|
|
4557
|
+
for (const entry of rankedFallback) {
|
|
4558
|
+
symbolCandidates.set(entry.candidate.id, {
|
|
4559
|
+
id: entry.candidate.id,
|
|
4560
|
+
score: Math.min(0.94, Math.max(entry.candidate.score, 0.8 + entry.overlapScore * 0.1)),
|
|
4561
|
+
metadata: entry.candidate.metadata
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4567
|
+
return withFallback.slice(0, Math.max(limit * 2, limit));
|
|
4568
|
+
}
|
|
4569
|
+
function buildIdentifierDefinitionLane(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4570
|
+
if (!prioritizeSourcePaths) {
|
|
4571
|
+
return [];
|
|
4572
|
+
}
|
|
4573
|
+
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
4574
|
+
if (!primaryHint) {
|
|
4575
|
+
return [];
|
|
4576
|
+
}
|
|
4577
|
+
const hints = [primaryHint, ...extractIdentifierHints(query), ...extractCodeTermHints(query)].slice(0, 8);
|
|
4578
|
+
const scored = candidates.filter(
|
|
4579
|
+
(candidate) => isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType)
|
|
4580
|
+
).map((candidate) => {
|
|
4581
|
+
const matchScore = scoreIdentifierMatch(candidate.metadata.name, candidate.metadata.filePath, hints);
|
|
4582
|
+
return {
|
|
4583
|
+
candidate,
|
|
4584
|
+
matchScore
|
|
4585
|
+
};
|
|
4586
|
+
}).filter((entry) => entry.matchScore > 0).sort((a, b) => {
|
|
4587
|
+
if (b.matchScore !== a.matchScore) return b.matchScore - a.matchScore;
|
|
4588
|
+
if (b.candidate.score !== a.candidate.score) return b.candidate.score - a.candidate.score;
|
|
4589
|
+
return a.candidate.id.localeCompare(b.candidate.id);
|
|
4590
|
+
}).slice(0, Math.max(limit * 2, 10));
|
|
4591
|
+
return scored.map((entry) => ({
|
|
4592
|
+
id: entry.candidate.id,
|
|
4593
|
+
score: Math.min(1, 0.9 + entry.matchScore * 0.09),
|
|
4594
|
+
metadata: entry.candidate.metadata
|
|
4595
|
+
}));
|
|
4596
|
+
}
|
|
4597
|
+
function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
4598
|
+
if (symbolLane.length === 0) {
|
|
4599
|
+
return hybridLane.slice(0, limit);
|
|
4600
|
+
}
|
|
4601
|
+
const out = [];
|
|
4602
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4603
|
+
for (const candidate of symbolLane) {
|
|
4604
|
+
if (seen.has(candidate.id)) continue;
|
|
4605
|
+
out.push(candidate);
|
|
4606
|
+
seen.add(candidate.id);
|
|
4607
|
+
if (out.length >= limit) return out;
|
|
4608
|
+
}
|
|
4609
|
+
for (const candidate of hybridLane) {
|
|
4610
|
+
if (seen.has(candidate.id)) continue;
|
|
4611
|
+
out.push(candidate);
|
|
4612
|
+
seen.add(candidate.id);
|
|
4613
|
+
if (out.length >= limit) return out;
|
|
4614
|
+
}
|
|
4615
|
+
return out;
|
|
4616
|
+
}
|
|
4617
|
+
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
4618
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4619
|
+
for (const candidate of semanticCandidates) {
|
|
4620
|
+
byId.set(candidate.id, candidate);
|
|
4621
|
+
}
|
|
4622
|
+
for (const candidate of keywordCandidates) {
|
|
4623
|
+
const existing = byId.get(candidate.id);
|
|
4624
|
+
if (!existing || candidate.score > existing.score) {
|
|
4625
|
+
byId.set(candidate.id, candidate);
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
return Array.from(byId.values());
|
|
4629
|
+
}
|
|
4630
|
+
var Indexer = class {
|
|
4631
|
+
config;
|
|
4632
|
+
projectRoot;
|
|
4633
|
+
indexPath;
|
|
4634
|
+
store = null;
|
|
4635
|
+
invertedIndex = null;
|
|
4636
|
+
database = null;
|
|
4637
|
+
provider = null;
|
|
4638
|
+
configuredProviderInfo = null;
|
|
4639
|
+
fileHashCache = /* @__PURE__ */ new Map();
|
|
4640
|
+
fileHashCachePath = "";
|
|
4641
|
+
failedBatchesPath = "";
|
|
4642
|
+
currentBranch = "default";
|
|
4643
|
+
baseBranch = "main";
|
|
4644
|
+
logger;
|
|
4645
|
+
queryEmbeddingCache = /* @__PURE__ */ new Map();
|
|
4646
|
+
maxQueryCacheSize = 100;
|
|
4647
|
+
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
4648
|
+
querySimilarityThreshold = 0.85;
|
|
4649
|
+
indexCompatibility = null;
|
|
4650
|
+
indexingLockPath = "";
|
|
4651
|
+
constructor(projectRoot, config) {
|
|
4652
|
+
this.projectRoot = projectRoot;
|
|
4653
|
+
this.config = config;
|
|
4654
|
+
this.indexPath = this.getIndexPath();
|
|
4655
|
+
this.fileHashCachePath = path6.join(this.indexPath, "file-hashes.json");
|
|
4656
|
+
this.failedBatchesPath = path6.join(this.indexPath, "failed-batches.json");
|
|
4657
|
+
this.indexingLockPath = path6.join(this.indexPath, "indexing.lock");
|
|
4658
|
+
this.logger = initializeLogger(config.debug);
|
|
4659
|
+
}
|
|
4660
|
+
getIndexPath() {
|
|
4661
|
+
if (this.config.scope === "global") {
|
|
4662
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4663
|
+
return path6.join(homeDir, ".opencode", "global-index");
|
|
4664
|
+
}
|
|
4665
|
+
return path6.join(this.projectRoot, ".opencode", "index");
|
|
4666
|
+
}
|
|
4667
|
+
loadFileHashCache() {
|
|
4668
|
+
try {
|
|
4669
|
+
if (existsSync4(this.fileHashCachePath)) {
|
|
4670
|
+
const data = readFileSync5(this.fileHashCachePath, "utf-8");
|
|
4671
|
+
const parsed = JSON.parse(data);
|
|
4672
|
+
this.fileHashCache = new Map(Object.entries(parsed));
|
|
4673
|
+
}
|
|
4674
|
+
} catch {
|
|
4675
|
+
this.fileHashCache = /* @__PURE__ */ new Map();
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
saveFileHashCache() {
|
|
4679
|
+
const obj = {};
|
|
4680
|
+
for (const [k, v] of this.fileHashCache) {
|
|
4681
|
+
obj[k] = v;
|
|
4682
|
+
}
|
|
4683
|
+
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
4684
|
+
}
|
|
4685
|
+
atomicWriteSync(targetPath, data) {
|
|
4686
|
+
const tempPath = `${targetPath}.tmp`;
|
|
4687
|
+
writeFileSync2(tempPath, data);
|
|
4688
|
+
renameSync(tempPath, targetPath);
|
|
4689
|
+
}
|
|
4690
|
+
checkForInterruptedIndexing() {
|
|
4691
|
+
return existsSync4(this.indexingLockPath);
|
|
4692
|
+
}
|
|
4693
|
+
acquireIndexingLock() {
|
|
4694
|
+
const lockData = {
|
|
4695
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4696
|
+
pid: process.pid
|
|
4697
|
+
};
|
|
4698
|
+
writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
|
|
4699
|
+
}
|
|
4700
|
+
releaseIndexingLock() {
|
|
4701
|
+
if (existsSync4(this.indexingLockPath)) {
|
|
4702
|
+
unlinkSync(this.indexingLockPath);
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
async recoverFromInterruptedIndexing() {
|
|
4706
|
+
this.logger.warn("Detected interrupted indexing session, recovering...");
|
|
4707
|
+
if (existsSync4(this.fileHashCachePath)) {
|
|
4708
|
+
unlinkSync(this.fileHashCachePath);
|
|
4709
|
+
}
|
|
4710
|
+
await this.healthCheck();
|
|
4711
|
+
this.releaseIndexingLock();
|
|
4712
|
+
this.logger.info("Recovery complete, next index will re-process all files");
|
|
4713
|
+
}
|
|
4714
|
+
loadFailedBatches() {
|
|
4715
|
+
try {
|
|
4716
|
+
if (existsSync4(this.failedBatchesPath)) {
|
|
4717
|
+
const data = readFileSync5(this.failedBatchesPath, "utf-8");
|
|
4718
|
+
return JSON.parse(data);
|
|
4719
|
+
}
|
|
4720
|
+
} catch {
|
|
4721
|
+
return [];
|
|
4722
|
+
}
|
|
4723
|
+
return [];
|
|
4724
|
+
}
|
|
4725
|
+
saveFailedBatches(batches) {
|
|
4726
|
+
if (batches.length === 0) {
|
|
4727
|
+
if (existsSync4(this.failedBatchesPath)) {
|
|
4728
|
+
fsPromises2.unlink(this.failedBatchesPath).catch(() => {
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
4734
|
+
}
|
|
4735
|
+
addFailedBatch(batch, error) {
|
|
4736
|
+
const existing = this.loadFailedBatches();
|
|
4737
|
+
existing.push({
|
|
4738
|
+
chunks: batch,
|
|
4739
|
+
error,
|
|
3547
4740
|
attemptCount: 1,
|
|
3548
4741
|
lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3549
4742
|
});
|
|
@@ -3596,13 +4789,13 @@ var Indexer = class {
|
|
|
3596
4789
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
3597
4790
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
3598
4791
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
3599
|
-
const storePath =
|
|
4792
|
+
const storePath = path6.join(this.indexPath, "vectors");
|
|
3600
4793
|
this.store = new VectorStore(storePath, dimensions);
|
|
3601
|
-
const indexFilePath =
|
|
4794
|
+
const indexFilePath = path6.join(this.indexPath, "vectors.usearch");
|
|
3602
4795
|
if (existsSync4(indexFilePath)) {
|
|
3603
4796
|
this.store.load();
|
|
3604
4797
|
}
|
|
3605
|
-
const invertedIndexPath =
|
|
4798
|
+
const invertedIndexPath = path6.join(this.indexPath, "inverted-index.json");
|
|
3606
4799
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
3607
4800
|
try {
|
|
3608
4801
|
this.invertedIndex.load();
|
|
@@ -3612,7 +4805,7 @@ var Indexer = class {
|
|
|
3612
4805
|
}
|
|
3613
4806
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
3614
4807
|
}
|
|
3615
|
-
const dbPath =
|
|
4808
|
+
const dbPath = path6.join(this.indexPath, "codebase.db");
|
|
3616
4809
|
const dbIsNew = !existsSync4(dbPath);
|
|
3617
4810
|
this.database = new Database(dbPath);
|
|
3618
4811
|
if (this.checkForInterruptedIndexing()) {
|
|
@@ -3891,7 +5084,7 @@ var Indexer = class {
|
|
|
3891
5084
|
for (const parsed of parsedFiles) {
|
|
3892
5085
|
currentFilePaths.add(parsed.path);
|
|
3893
5086
|
if (parsed.chunks.length === 0) {
|
|
3894
|
-
const relativePath =
|
|
5087
|
+
const relativePath = path6.relative(this.projectRoot, parsed.path);
|
|
3895
5088
|
stats.parseFailures.push(relativePath);
|
|
3896
5089
|
}
|
|
3897
5090
|
let fileChunkCount = 0;
|
|
@@ -3936,6 +5129,79 @@ var Indexer = class {
|
|
|
3936
5129
|
if (chunkDataBatch.length > 0) {
|
|
3937
5130
|
database.upsertChunksBatch(chunkDataBatch);
|
|
3938
5131
|
}
|
|
5132
|
+
const allSymbolIds = /* @__PURE__ */ new Set();
|
|
5133
|
+
const symbolsByFile = /* @__PURE__ */ new Map();
|
|
5134
|
+
for (let i = 0; i < parsedFiles.length; i++) {
|
|
5135
|
+
const parsed = parsedFiles[i];
|
|
5136
|
+
const changedFile = changedFiles[i];
|
|
5137
|
+
database.deleteCallEdgesByFile(parsed.path);
|
|
5138
|
+
database.deleteSymbolsByFile(parsed.path);
|
|
5139
|
+
const fileSymbols = [];
|
|
5140
|
+
for (const chunk of parsed.chunks) {
|
|
5141
|
+
if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
|
|
5142
|
+
const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
|
|
5143
|
+
const symbol = {
|
|
5144
|
+
id: symbolId,
|
|
5145
|
+
filePath: parsed.path,
|
|
5146
|
+
name: chunk.name,
|
|
5147
|
+
kind: chunk.chunkType,
|
|
5148
|
+
startLine: chunk.startLine,
|
|
5149
|
+
startCol: 0,
|
|
5150
|
+
endLine: chunk.endLine,
|
|
5151
|
+
endCol: 0,
|
|
5152
|
+
language: chunk.language
|
|
5153
|
+
};
|
|
5154
|
+
fileSymbols.push(symbol);
|
|
5155
|
+
allSymbolIds.add(symbolId);
|
|
5156
|
+
}
|
|
5157
|
+
const symbolsByName = /* @__PURE__ */ new Map();
|
|
5158
|
+
for (const symbol of fileSymbols) {
|
|
5159
|
+
const existing = symbolsByName.get(symbol.name) ?? [];
|
|
5160
|
+
existing.push(symbol);
|
|
5161
|
+
symbolsByName.set(symbol.name, existing);
|
|
5162
|
+
}
|
|
5163
|
+
if (fileSymbols.length > 0) {
|
|
5164
|
+
database.upsertSymbolsBatch(fileSymbols);
|
|
5165
|
+
symbolsByFile.set(parsed.path, fileSymbols);
|
|
5166
|
+
}
|
|
5167
|
+
const fileLanguage = parsed.chunks[0]?.language;
|
|
5168
|
+
if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
|
|
5169
|
+
const callSites = extractCalls(changedFile.content, fileLanguage);
|
|
5170
|
+
if (callSites.length === 0) continue;
|
|
5171
|
+
const edges = [];
|
|
5172
|
+
for (const site of callSites) {
|
|
5173
|
+
const enclosingSymbol = fileSymbols.find(
|
|
5174
|
+
(sym) => site.line >= sym.startLine && site.line <= sym.endLine
|
|
5175
|
+
);
|
|
5176
|
+
if (!enclosingSymbol) continue;
|
|
5177
|
+
const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
|
|
5178
|
+
edges.push({
|
|
5179
|
+
id: edgeId,
|
|
5180
|
+
fromSymbolId: enclosingSymbol.id,
|
|
5181
|
+
targetName: site.calleeName,
|
|
5182
|
+
toSymbolId: void 0,
|
|
5183
|
+
callType: site.callType,
|
|
5184
|
+
line: site.line,
|
|
5185
|
+
col: site.column,
|
|
5186
|
+
isResolved: false
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
if (edges.length > 0) {
|
|
5190
|
+
database.upsertCallEdgesBatch(edges);
|
|
5191
|
+
for (const edge of edges) {
|
|
5192
|
+
const candidates = symbolsByName.get(edge.targetName);
|
|
5193
|
+
if (candidates && candidates.length === 1) {
|
|
5194
|
+
database.resolveCallEdge(edge.id, candidates[0].id);
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
for (const filePath of unchangedFilePaths) {
|
|
5200
|
+
const existingSymbols = database.getSymbolsByFile(filePath);
|
|
5201
|
+
for (const sym of existingSymbols) {
|
|
5202
|
+
allSymbolIds.add(sym.id);
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
3939
5205
|
let removedCount = 0;
|
|
3940
5206
|
for (const [chunkId] of existingChunks) {
|
|
3941
5207
|
if (!currentChunkIds.has(chunkId)) {
|
|
@@ -3957,6 +5223,8 @@ var Indexer = class {
|
|
|
3957
5223
|
if (pendingChunks.length === 0 && removedCount === 0) {
|
|
3958
5224
|
database.clearBranch(this.currentBranch);
|
|
3959
5225
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5226
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5227
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
3960
5228
|
this.fileHashCache = currentFileHashes;
|
|
3961
5229
|
this.saveFileHashCache();
|
|
3962
5230
|
stats.durationMs = Date.now() - startTime;
|
|
@@ -3973,6 +5241,8 @@ var Indexer = class {
|
|
|
3973
5241
|
if (pendingChunks.length === 0) {
|
|
3974
5242
|
database.clearBranch(this.currentBranch);
|
|
3975
5243
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5244
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5245
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
3976
5246
|
store.save();
|
|
3977
5247
|
invertedIndex.save();
|
|
3978
5248
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4025,7 +5295,7 @@ var Indexer = class {
|
|
|
4025
5295
|
for (const batch of dynamicBatches) {
|
|
4026
5296
|
queue.add(async () => {
|
|
4027
5297
|
if (rateLimitBackoffMs > 0) {
|
|
4028
|
-
await new Promise((
|
|
5298
|
+
await new Promise((resolve5) => setTimeout(resolve5, rateLimitBackoffMs));
|
|
4029
5299
|
}
|
|
4030
5300
|
try {
|
|
4031
5301
|
const result = await pRetry(
|
|
@@ -4113,6 +5383,8 @@ var Indexer = class {
|
|
|
4113
5383
|
});
|
|
4114
5384
|
database.clearBranch(this.currentBranch);
|
|
4115
5385
|
database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
|
|
5386
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
5387
|
+
database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
|
|
4116
5388
|
store.save();
|
|
4117
5389
|
invertedIndex.save();
|
|
4118
5390
|
this.fileHashCache = currentFileHashes;
|
|
@@ -4223,15 +5495,23 @@ var Indexer = class {
|
|
|
4223
5495
|
}
|
|
4224
5496
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
4225
5497
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
5498
|
+
const fusionStrategy = this.config.search.fusionStrategy;
|
|
5499
|
+
const rrfK = this.config.search.rrfK;
|
|
5500
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
4226
5501
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5502
|
+
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
4227
5503
|
this.logger.search("debug", "Starting search", {
|
|
4228
5504
|
query,
|
|
4229
5505
|
maxResults,
|
|
4230
5506
|
hybridWeight,
|
|
5507
|
+
fusionStrategy,
|
|
5508
|
+
rrfK,
|
|
5509
|
+
rerankTopN,
|
|
4231
5510
|
filterByBranch
|
|
4232
5511
|
});
|
|
4233
5512
|
const embeddingStartTime = performance2.now();
|
|
4234
|
-
const
|
|
5513
|
+
const embeddingQuery = stripFilePathHint(query);
|
|
5514
|
+
const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
4235
5515
|
const embeddingMs = performance2.now() - embeddingStartTime;
|
|
4236
5516
|
const vectorStartTime = performance2.now();
|
|
4237
5517
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
@@ -4239,36 +5519,102 @@ var Indexer = class {
|
|
|
4239
5519
|
const keywordStartTime = performance2.now();
|
|
4240
5520
|
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
4241
5521
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
4242
|
-
const fusionStartTime = performance2.now();
|
|
4243
|
-
const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
|
|
4244
|
-
const fusionMs = performance2.now() - fusionStartTime;
|
|
4245
5522
|
let branchChunkIds = null;
|
|
4246
5523
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4247
5524
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4248
5525
|
}
|
|
4249
|
-
const
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
5526
|
+
const prefilterStartTime = performance2.now();
|
|
5527
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5528
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5529
|
+
const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
|
|
5530
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5531
|
+
const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
|
|
5532
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5533
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5534
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5535
|
+
branch: this.currentBranch
|
|
5536
|
+
});
|
|
5537
|
+
}
|
|
5538
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5539
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5540
|
+
branch: this.currentBranch
|
|
5541
|
+
});
|
|
5542
|
+
}
|
|
5543
|
+
if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
|
|
5544
|
+
this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
|
|
5545
|
+
branch: this.currentBranch
|
|
5546
|
+
});
|
|
5547
|
+
}
|
|
5548
|
+
const fusionStartTime = performance2.now();
|
|
5549
|
+
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
5550
|
+
fusionStrategy,
|
|
5551
|
+
rrfK,
|
|
5552
|
+
rerankTopN,
|
|
5553
|
+
limit: maxResults,
|
|
5554
|
+
hybridWeight,
|
|
5555
|
+
prioritizeSourcePaths: sourceIntent
|
|
5556
|
+
});
|
|
5557
|
+
const fusionMs = performance2.now() - fusionStartTime;
|
|
5558
|
+
const rescued = promoteIdentifierMatches(
|
|
5559
|
+
query,
|
|
5560
|
+
combined,
|
|
5561
|
+
semanticCandidates,
|
|
5562
|
+
keywordCandidates,
|
|
5563
|
+
database,
|
|
5564
|
+
branchChunkIds,
|
|
5565
|
+
sourceIntent
|
|
5566
|
+
);
|
|
5567
|
+
const union = unionCandidates(semanticCandidates, keywordCandidates);
|
|
5568
|
+
const deterministicIdentifierLane = buildDeterministicIdentifierPass(
|
|
5569
|
+
query,
|
|
5570
|
+
union,
|
|
5571
|
+
maxResults,
|
|
5572
|
+
sourceIntent
|
|
5573
|
+
);
|
|
5574
|
+
const identifierLane = buildIdentifierDefinitionLane(
|
|
5575
|
+
query,
|
|
5576
|
+
union,
|
|
5577
|
+
maxResults,
|
|
5578
|
+
sourceIntent
|
|
5579
|
+
);
|
|
5580
|
+
const symbolLane = buildSymbolDefinitionLane(
|
|
5581
|
+
query,
|
|
5582
|
+
database,
|
|
5583
|
+
branchChunkIds,
|
|
5584
|
+
maxResults,
|
|
5585
|
+
union,
|
|
5586
|
+
sourceIntent
|
|
5587
|
+
);
|
|
5588
|
+
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5589
|
+
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5590
|
+
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5591
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
|
|
5592
|
+
const baseFiltered = tiered.filter((r) => {
|
|
5593
|
+
if (r.score < this.config.search.minScore) return false;
|
|
5594
|
+
if (options?.fileType) {
|
|
5595
|
+
const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
|
|
5596
|
+
if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
|
|
5597
|
+
}
|
|
5598
|
+
if (options?.directory) {
|
|
5599
|
+
const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
|
|
5600
|
+
if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
|
|
5601
|
+
}
|
|
5602
|
+
if (options?.chunkType) {
|
|
5603
|
+
if (r.metadata.chunkType !== options.chunkType) return false;
|
|
5604
|
+
}
|
|
5605
|
+
return true;
|
|
5606
|
+
});
|
|
5607
|
+
const implementationOnly = baseFiltered.filter(
|
|
5608
|
+
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5609
|
+
);
|
|
5610
|
+
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
5611
|
+
const totalSearchMs = performance2.now() - searchStartTime;
|
|
5612
|
+
this.logger.recordSearch(totalSearchMs, {
|
|
5613
|
+
embeddingMs,
|
|
5614
|
+
vectorMs,
|
|
5615
|
+
keywordMs,
|
|
5616
|
+
fusionMs
|
|
5617
|
+
});
|
|
4272
5618
|
this.logger.search("info", "Search complete", {
|
|
4273
5619
|
query,
|
|
4274
5620
|
results: filtered.length,
|
|
@@ -4276,6 +5622,7 @@ var Indexer = class {
|
|
|
4276
5622
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4277
5623
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
4278
5624
|
keywordMs: Math.round(keywordMs * 100) / 100,
|
|
5625
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100,
|
|
4279
5626
|
fusionMs: Math.round(fusionMs * 100) / 100
|
|
4280
5627
|
});
|
|
4281
5628
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
@@ -4329,34 +5676,6 @@ var Indexer = class {
|
|
|
4329
5676
|
results.sort((a, b) => b.score - a.score);
|
|
4330
5677
|
return results.slice(0, limit);
|
|
4331
5678
|
}
|
|
4332
|
-
fuseResults(semanticResults, keywordResults, keywordWeight, limit) {
|
|
4333
|
-
const semanticWeight = 1 - keywordWeight;
|
|
4334
|
-
const fusedScores = /* @__PURE__ */ new Map();
|
|
4335
|
-
for (const r of semanticResults) {
|
|
4336
|
-
fusedScores.set(r.id, {
|
|
4337
|
-
score: r.score * semanticWeight,
|
|
4338
|
-
metadata: r.metadata
|
|
4339
|
-
});
|
|
4340
|
-
}
|
|
4341
|
-
for (const r of keywordResults) {
|
|
4342
|
-
const existing = fusedScores.get(r.id);
|
|
4343
|
-
if (existing) {
|
|
4344
|
-
existing.score += r.score * keywordWeight;
|
|
4345
|
-
} else {
|
|
4346
|
-
fusedScores.set(r.id, {
|
|
4347
|
-
score: r.score * keywordWeight,
|
|
4348
|
-
metadata: r.metadata
|
|
4349
|
-
});
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
const results = Array.from(fusedScores.entries()).map(([id, data]) => ({
|
|
4353
|
-
id,
|
|
4354
|
-
score: data.score,
|
|
4355
|
-
metadata: data.metadata
|
|
4356
|
-
}));
|
|
4357
|
-
results.sort((a, b) => b.score - a.score);
|
|
4358
|
-
return results.slice(0, limit);
|
|
4359
|
-
}
|
|
4360
5679
|
async getStatus() {
|
|
4361
5680
|
const { store, configuredProviderInfo } = await this.ensureInitialized();
|
|
4362
5681
|
return {
|
|
@@ -4379,6 +5698,7 @@ var Indexer = class {
|
|
|
4379
5698
|
this.fileHashCache.clear();
|
|
4380
5699
|
this.saveFileHashCache();
|
|
4381
5700
|
database.clearBranch(this.currentBranch);
|
|
5701
|
+
database.clearBranchSymbols(this.currentBranch);
|
|
4382
5702
|
database.deleteMetadata("index.version");
|
|
4383
5703
|
database.deleteMetadata("index.embeddingProvider");
|
|
4384
5704
|
database.deleteMetadata("index.embeddingModel");
|
|
@@ -4407,6 +5727,8 @@ var Indexer = class {
|
|
|
4407
5727
|
removedCount++;
|
|
4408
5728
|
}
|
|
4409
5729
|
database.deleteChunksByFile(filePath);
|
|
5730
|
+
database.deleteCallEdgesByFile(filePath);
|
|
5731
|
+
database.deleteSymbolsByFile(filePath);
|
|
4410
5732
|
removedFilePaths.push(filePath);
|
|
4411
5733
|
}
|
|
4412
5734
|
}
|
|
@@ -4416,6 +5738,8 @@ var Indexer = class {
|
|
|
4416
5738
|
}
|
|
4417
5739
|
const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
|
|
4418
5740
|
const gcOrphanChunks = database.gcOrphanChunks();
|
|
5741
|
+
const gcOrphanSymbols = database.gcOrphanSymbols();
|
|
5742
|
+
const gcOrphanCallEdges = database.gcOrphanCallEdges();
|
|
4419
5743
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
4420
5744
|
this.logger.gc("info", "Health check complete", {
|
|
4421
5745
|
removedStale: removedCount,
|
|
@@ -4423,7 +5747,7 @@ var Indexer = class {
|
|
|
4423
5747
|
orphanChunks: gcOrphanChunks,
|
|
4424
5748
|
removedFiles: removedFilePaths.length
|
|
4425
5749
|
});
|
|
4426
|
-
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
|
|
5750
|
+
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
4427
5751
|
}
|
|
4428
5752
|
async retryFailedBatches() {
|
|
4429
5753
|
const { store, provider, invertedIndex } = await this.ensureInitialized();
|
|
@@ -4529,9 +5853,29 @@ var Indexer = class {
|
|
|
4529
5853
|
if (filterByBranch && this.currentBranch !== "default") {
|
|
4530
5854
|
branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
|
|
4531
5855
|
}
|
|
4532
|
-
const
|
|
5856
|
+
const prefilterStartTime = performance2.now();
|
|
5857
|
+
const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
|
|
5858
|
+
const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
|
|
5859
|
+
const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
|
|
5860
|
+
const prefilterMs = performance2.now() - prefilterStartTime;
|
|
5861
|
+
if (branchChunkIds && branchChunkIds.size === 0) {
|
|
5862
|
+
this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
|
|
5863
|
+
branch: this.currentBranch
|
|
5864
|
+
});
|
|
5865
|
+
}
|
|
5866
|
+
if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
|
|
5867
|
+
this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
|
|
5868
|
+
branch: this.currentBranch
|
|
5869
|
+
});
|
|
5870
|
+
}
|
|
5871
|
+
const rerankTopN = this.config.search.rerankTopN;
|
|
5872
|
+
const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
|
|
5873
|
+
rerankTopN,
|
|
5874
|
+
limit,
|
|
5875
|
+
prioritizeSourcePaths: false
|
|
5876
|
+
});
|
|
5877
|
+
const filtered = ranked.filter((r) => {
|
|
4533
5878
|
if (r.score < this.config.search.minScore) return false;
|
|
4534
|
-
if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
|
|
4535
5879
|
if (options?.excludeFile) {
|
|
4536
5880
|
if (r.metadata.filePath === options.excludeFile) return false;
|
|
4537
5881
|
}
|
|
@@ -4560,7 +5904,8 @@ var Indexer = class {
|
|
|
4560
5904
|
results: filtered.length,
|
|
4561
5905
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
4562
5906
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
4563
|
-
vectorMs: Math.round(vectorMs * 100) / 100
|
|
5907
|
+
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
5908
|
+
prefilterMs: Math.round(prefilterMs * 100) / 100
|
|
4564
5909
|
});
|
|
4565
5910
|
return Promise.all(
|
|
4566
5911
|
filtered.map(async (r) => {
|
|
@@ -4589,9 +5934,906 @@ var Indexer = class {
|
|
|
4589
5934
|
})
|
|
4590
5935
|
);
|
|
4591
5936
|
}
|
|
5937
|
+
async getCallers(targetName) {
|
|
5938
|
+
const { database } = await this.ensureInitialized();
|
|
5939
|
+
return database.getCallersWithContext(targetName, this.currentBranch);
|
|
5940
|
+
}
|
|
5941
|
+
async getCallees(symbolId) {
|
|
5942
|
+
const { database } = await this.ensureInitialized();
|
|
5943
|
+
return database.getCallees(symbolId, this.currentBranch);
|
|
5944
|
+
}
|
|
4592
5945
|
};
|
|
4593
5946
|
|
|
5947
|
+
// src/eval/budget.ts
|
|
5948
|
+
function evaluateBudgetGate(budget, summary, comparison) {
|
|
5949
|
+
const BASELINE_P95_EPSILON_MS = 1e-3;
|
|
5950
|
+
const violations = [];
|
|
5951
|
+
const { thresholds } = budget;
|
|
5952
|
+
if (thresholds.minHitAt5 !== void 0 && summary.metrics.hitAt5 < thresholds.minHitAt5) {
|
|
5953
|
+
violations.push({
|
|
5954
|
+
metric: "minHitAt5",
|
|
5955
|
+
message: `Hit@5 ${summary.metrics.hitAt5.toFixed(4)} is below minimum ${thresholds.minHitAt5.toFixed(4)}`
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
if (thresholds.minMrrAt10 !== void 0 && summary.metrics.mrrAt10 < thresholds.minMrrAt10) {
|
|
5959
|
+
violations.push({
|
|
5960
|
+
metric: "minMrrAt10",
|
|
5961
|
+
message: `MRR@10 ${summary.metrics.mrrAt10.toFixed(4)} is below minimum ${thresholds.minMrrAt10.toFixed(4)}`
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
if (comparison) {
|
|
5965
|
+
if (thresholds.hitAt5MaxDrop !== void 0 && comparison.deltas.hitAt5.absolute < -thresholds.hitAt5MaxDrop) {
|
|
5966
|
+
violations.push({
|
|
5967
|
+
metric: "hitAt5MaxDrop",
|
|
5968
|
+
message: `Hit@5 drop ${comparison.deltas.hitAt5.absolute.toFixed(4)} exceeds allowed -${thresholds.hitAt5MaxDrop.toFixed(4)}`
|
|
5969
|
+
});
|
|
5970
|
+
}
|
|
5971
|
+
if (thresholds.mrrAt10MaxDrop !== void 0 && comparison.deltas.mrrAt10.absolute < -thresholds.mrrAt10MaxDrop) {
|
|
5972
|
+
violations.push({
|
|
5973
|
+
metric: "mrrAt10MaxDrop",
|
|
5974
|
+
message: `MRR@10 drop ${comparison.deltas.mrrAt10.absolute.toFixed(4)} exceeds allowed -${thresholds.mrrAt10MaxDrop.toFixed(4)}`
|
|
5975
|
+
});
|
|
5976
|
+
}
|
|
5977
|
+
if (thresholds.p95LatencyMaxMultiplier !== void 0) {
|
|
5978
|
+
const baselineP95 = comparison.deltas.latencyP95Ms.baseline;
|
|
5979
|
+
if (baselineP95 > BASELINE_P95_EPSILON_MS) {
|
|
5980
|
+
const allowed = baselineP95 * thresholds.p95LatencyMaxMultiplier;
|
|
5981
|
+
if (summary.metrics.latencyMs.p95 > allowed) {
|
|
5982
|
+
violations.push({
|
|
5983
|
+
metric: "p95LatencyMaxMultiplier",
|
|
5984
|
+
message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds allowed ${allowed.toFixed(3)}ms (${thresholds.p95LatencyMaxMultiplier.toFixed(2)}x baseline)`
|
|
5985
|
+
});
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
if (thresholds.p95LatencyMaxAbsoluteMs !== void 0 && summary.metrics.latencyMs.p95 > thresholds.p95LatencyMaxAbsoluteMs) {
|
|
5991
|
+
violations.push({
|
|
5992
|
+
metric: "p95LatencyMaxAbsoluteMs",
|
|
5993
|
+
message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds absolute maximum ${thresholds.p95LatencyMaxAbsoluteMs.toFixed(3)}ms`
|
|
5994
|
+
});
|
|
5995
|
+
}
|
|
5996
|
+
return {
|
|
5997
|
+
passed: violations.length === 0,
|
|
5998
|
+
budgetName: budget.name,
|
|
5999
|
+
violations
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
6002
|
+
|
|
6003
|
+
// src/eval/metrics.ts
|
|
6004
|
+
function percentile(values, p) {
|
|
6005
|
+
if (values.length === 0) return 0;
|
|
6006
|
+
if (values.length === 1) return values[0];
|
|
6007
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
6008
|
+
const x = p * (sorted.length - 1);
|
|
6009
|
+
const lowerIndex = Math.floor(x);
|
|
6010
|
+
const upperIndex = Math.ceil(x);
|
|
6011
|
+
if (lowerIndex === upperIndex) {
|
|
6012
|
+
return sorted[lowerIndex];
|
|
6013
|
+
}
|
|
6014
|
+
const fraction = x - lowerIndex;
|
|
6015
|
+
return sorted[lowerIndex] + fraction * (sorted[upperIndex] - sorted[lowerIndex]);
|
|
6016
|
+
}
|
|
6017
|
+
function normalizePath(input) {
|
|
6018
|
+
return input.replace(/\\/g, "/");
|
|
6019
|
+
}
|
|
6020
|
+
function uniqueResultsByPath(results) {
|
|
6021
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6022
|
+
const unique = [];
|
|
6023
|
+
for (const result of results) {
|
|
6024
|
+
const normalized = normalizePath(result.filePath);
|
|
6025
|
+
if (seen.has(normalized)) continue;
|
|
6026
|
+
seen.add(normalized);
|
|
6027
|
+
unique.push(result);
|
|
6028
|
+
}
|
|
6029
|
+
return unique;
|
|
6030
|
+
}
|
|
6031
|
+
function pathMatchesExpected(actualPath, expectedPath) {
|
|
6032
|
+
const actual = normalizePath(actualPath);
|
|
6033
|
+
const expected = normalizePath(expectedPath);
|
|
6034
|
+
if (actual === expected) return true;
|
|
6035
|
+
return actual.endsWith(`/${expected}`) || expected.endsWith(`/${actual}`);
|
|
6036
|
+
}
|
|
6037
|
+
function getRelevantPaths(query) {
|
|
6038
|
+
const fromExact = query.expected.filePath ? [query.expected.filePath] : [];
|
|
6039
|
+
const fromAcceptable = query.expected.acceptableFiles ?? [];
|
|
6040
|
+
return Array.from(/* @__PURE__ */ new Set([...fromExact, ...fromAcceptable]));
|
|
6041
|
+
}
|
|
6042
|
+
function isRelevantResult(filePath, relevantPaths) {
|
|
6043
|
+
return relevantPaths.some((expected) => pathMatchesExpected(filePath, expected));
|
|
6044
|
+
}
|
|
6045
|
+
function reciprocalRankAtK(results, relevantPaths, k) {
|
|
6046
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6047
|
+
for (let i = 0; i < top.length; i += 1) {
|
|
6048
|
+
if (isRelevantResult(top[i].filePath, relevantPaths)) {
|
|
6049
|
+
return 1 / (i + 1);
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
return 0;
|
|
6053
|
+
}
|
|
6054
|
+
function ndcgAtK(results, relevantPaths, k) {
|
|
6055
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6056
|
+
const dcg = top.reduce((sum, result, i) => {
|
|
6057
|
+
const rel = isRelevantResult(result.filePath, relevantPaths) ? 1 : 0;
|
|
6058
|
+
return sum + rel / Math.log2(i + 2);
|
|
6059
|
+
}, 0);
|
|
6060
|
+
const idealLen = Math.min(k, relevantPaths.length);
|
|
6061
|
+
const idcg = Array.from({ length: idealLen }, (_, i) => 1 / Math.log2(i + 2)).reduce(
|
|
6062
|
+
(sum, value) => sum + value,
|
|
6063
|
+
0
|
|
6064
|
+
);
|
|
6065
|
+
return idcg === 0 ? 0 : dcg / idcg;
|
|
6066
|
+
}
|
|
6067
|
+
function isDocsOrTestsPath(filePath) {
|
|
6068
|
+
const lowered = normalizePath(filePath).toLowerCase();
|
|
6069
|
+
return lowered.includes("/docs/") || lowered.includes("/test/") || lowered.includes("/tests/") || lowered.includes("readme") || lowered.includes("/benchmarks/");
|
|
6070
|
+
}
|
|
6071
|
+
function classifyFailureBucket(query, results, k) {
|
|
6072
|
+
const relevantPaths = getRelevantPaths(query);
|
|
6073
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6074
|
+
const hasRelevantTopK = top.some((result) => isRelevantResult(result.filePath, relevantPaths));
|
|
6075
|
+
if (!hasRelevantTopK) {
|
|
6076
|
+
return "no-relevant-hit-top-k";
|
|
6077
|
+
}
|
|
6078
|
+
if (query.expected.symbol) {
|
|
6079
|
+
const hasSymbol = top.some(
|
|
6080
|
+
(result) => isRelevantResult(result.filePath, relevantPaths) && result.name === query.expected.symbol
|
|
6081
|
+
);
|
|
6082
|
+
if (!hasSymbol) return "wrong-symbol";
|
|
6083
|
+
}
|
|
6084
|
+
const top1 = top[0];
|
|
6085
|
+
if (top1 && !isRelevantResult(top1.filePath, relevantPaths) && isDocsOrTestsPath(top1.filePath)) {
|
|
6086
|
+
return "docs-tests-outranking-source";
|
|
6087
|
+
}
|
|
6088
|
+
if (top1 && !isRelevantResult(top1.filePath, relevantPaths)) {
|
|
6089
|
+
return "wrong-file";
|
|
6090
|
+
}
|
|
6091
|
+
return void 0;
|
|
6092
|
+
}
|
|
6093
|
+
function buildPerQueryResult(query, results, latencyMs, k) {
|
|
6094
|
+
const relevantPaths = getRelevantPaths(query);
|
|
6095
|
+
const deduped = uniqueResultsByPath(results);
|
|
6096
|
+
const hitAt = (cutoff) => deduped.slice(0, cutoff).some((result) => isRelevantResult(result.filePath, relevantPaths));
|
|
6097
|
+
const perQuery = {
|
|
6098
|
+
id: query.id,
|
|
6099
|
+
query: query.query,
|
|
6100
|
+
queryType: query.queryType,
|
|
6101
|
+
latencyMs,
|
|
6102
|
+
hitAt1: hitAt(1),
|
|
6103
|
+
hitAt3: hitAt(3),
|
|
6104
|
+
hitAt5: hitAt(5),
|
|
6105
|
+
hitAt10: hitAt(10),
|
|
6106
|
+
reciprocalRankAt10: reciprocalRankAtK(deduped, relevantPaths, 10),
|
|
6107
|
+
ndcgAt10: ndcgAtK(deduped, relevantPaths, 10),
|
|
6108
|
+
failureBucket: classifyFailureBucket(query, results, k),
|
|
6109
|
+
results: deduped
|
|
6110
|
+
};
|
|
6111
|
+
return perQuery;
|
|
6112
|
+
}
|
|
6113
|
+
function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingTokensUsed, costPer1MTokensUsd) {
|
|
6114
|
+
const count = perQuery.length;
|
|
6115
|
+
const safeDiv = (value) => count === 0 ? 0 : value / count;
|
|
6116
|
+
const sum = {
|
|
6117
|
+
hitAt1: 0,
|
|
6118
|
+
hitAt3: 0,
|
|
6119
|
+
hitAt5: 0,
|
|
6120
|
+
hitAt10: 0,
|
|
6121
|
+
mrrAt10: 0,
|
|
6122
|
+
ndcgAt10: 0
|
|
6123
|
+
};
|
|
6124
|
+
const failureBuckets = {
|
|
6125
|
+
"wrong-file": 0,
|
|
6126
|
+
"wrong-symbol": 0,
|
|
6127
|
+
"docs-tests-outranking-source": 0,
|
|
6128
|
+
"no-relevant-hit-top-k": 0
|
|
6129
|
+
};
|
|
6130
|
+
const latencies = perQuery.map((item) => item.latencyMs);
|
|
6131
|
+
for (const query of perQuery) {
|
|
6132
|
+
if (query.hitAt1) sum.hitAt1 += 1;
|
|
6133
|
+
if (query.hitAt3) sum.hitAt3 += 1;
|
|
6134
|
+
if (query.hitAt5) sum.hitAt5 += 1;
|
|
6135
|
+
if (query.hitAt10) sum.hitAt10 += 1;
|
|
6136
|
+
sum.mrrAt10 += query.reciprocalRankAt10;
|
|
6137
|
+
sum.ndcgAt10 += query.ndcgAt10;
|
|
6138
|
+
if (query.failureBucket) {
|
|
6139
|
+
failureBuckets[query.failureBucket] += 1;
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
const queryTokens = queries.reduce((acc, q) => acc + estimateTokens(q.query), 0);
|
|
6143
|
+
return {
|
|
6144
|
+
hitAt1: safeDiv(sum.hitAt1),
|
|
6145
|
+
hitAt3: safeDiv(sum.hitAt3),
|
|
6146
|
+
hitAt5: safeDiv(sum.hitAt5),
|
|
6147
|
+
hitAt10: safeDiv(sum.hitAt10),
|
|
6148
|
+
mrrAt10: safeDiv(sum.mrrAt10),
|
|
6149
|
+
ndcgAt10: safeDiv(sum.ndcgAt10),
|
|
6150
|
+
latencyMs: {
|
|
6151
|
+
p50: percentile(latencies, 0.5),
|
|
6152
|
+
p95: percentile(latencies, 0.95),
|
|
6153
|
+
p99: percentile(latencies, 0.99)
|
|
6154
|
+
},
|
|
6155
|
+
tokenEstimate: {
|
|
6156
|
+
queryTokens,
|
|
6157
|
+
embeddingTokensUsed
|
|
6158
|
+
},
|
|
6159
|
+
embedding: {
|
|
6160
|
+
callCount: embeddingCallCount,
|
|
6161
|
+
estimatedCostUsd: embeddingTokensUsed / 1e6 * costPer1MTokensUsd,
|
|
6162
|
+
costPer1MTokensUsd
|
|
6163
|
+
},
|
|
6164
|
+
failureBuckets
|
|
6165
|
+
};
|
|
6166
|
+
}
|
|
6167
|
+
|
|
6168
|
+
// src/eval/schema.ts
|
|
6169
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
6170
|
+
function parseJsonFile(filePath) {
|
|
6171
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
6172
|
+
return JSON.parse(content);
|
|
6173
|
+
}
|
|
6174
|
+
function isRecord(value) {
|
|
6175
|
+
return typeof value === "object" && value !== null;
|
|
6176
|
+
}
|
|
6177
|
+
function isStringArray2(value) {
|
|
6178
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6179
|
+
}
|
|
6180
|
+
function asPositiveNumber(value, path10) {
|
|
6181
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
6182
|
+
throw new Error(`${path10} must be a non-negative number`);
|
|
6183
|
+
}
|
|
6184
|
+
return value;
|
|
6185
|
+
}
|
|
6186
|
+
function parseQueryType(value, path10) {
|
|
6187
|
+
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
6188
|
+
return value;
|
|
6189
|
+
}
|
|
6190
|
+
throw new Error(
|
|
6191
|
+
`${path10} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
6192
|
+
);
|
|
6193
|
+
}
|
|
6194
|
+
function parseExpected(input, path10) {
|
|
6195
|
+
if (!isRecord(input)) {
|
|
6196
|
+
throw new Error(`${path10} must be an object`);
|
|
6197
|
+
}
|
|
6198
|
+
const filePathRaw = input.filePath;
|
|
6199
|
+
const acceptableFilesRaw = input.acceptableFiles;
|
|
6200
|
+
const symbolRaw = input.symbol;
|
|
6201
|
+
const branchRaw = input.branch;
|
|
6202
|
+
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
6203
|
+
const acceptableFiles = isStringArray2(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
6204
|
+
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
6205
|
+
throw new Error(`${path10} must include either expected.filePath or expected.acceptableFiles`);
|
|
6206
|
+
}
|
|
6207
|
+
if (acceptableFilesRaw !== void 0 && !isStringArray2(acceptableFilesRaw)) {
|
|
6208
|
+
throw new Error(`${path10}.acceptableFiles must be an array of strings`);
|
|
6209
|
+
}
|
|
6210
|
+
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
6211
|
+
throw new Error(`${path10}.symbol must be a string when provided`);
|
|
6212
|
+
}
|
|
6213
|
+
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
6214
|
+
throw new Error(`${path10}.branch must be a string when provided`);
|
|
6215
|
+
}
|
|
6216
|
+
return {
|
|
6217
|
+
filePath,
|
|
6218
|
+
acceptableFiles,
|
|
6219
|
+
symbol: typeof symbolRaw === "string" ? symbolRaw : void 0,
|
|
6220
|
+
branch: typeof branchRaw === "string" ? branchRaw : void 0
|
|
6221
|
+
};
|
|
6222
|
+
}
|
|
6223
|
+
function parseQuery(input, index) {
|
|
6224
|
+
const path10 = `queries[${index}]`;
|
|
6225
|
+
if (!isRecord(input)) {
|
|
6226
|
+
throw new Error(`${path10} must be an object`);
|
|
6227
|
+
}
|
|
6228
|
+
const id = input.id;
|
|
6229
|
+
const query = input.query;
|
|
6230
|
+
const queryType = input.queryType;
|
|
6231
|
+
const expected = input.expected;
|
|
6232
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
6233
|
+
throw new Error(`${path10}.id must be a non-empty string`);
|
|
6234
|
+
}
|
|
6235
|
+
if (typeof query !== "string" || query.trim().length === 0) {
|
|
6236
|
+
throw new Error(`${path10}.query must be a non-empty string`);
|
|
6237
|
+
}
|
|
6238
|
+
return {
|
|
6239
|
+
id,
|
|
6240
|
+
query,
|
|
6241
|
+
queryType: parseQueryType(queryType, `${path10}.queryType`),
|
|
6242
|
+
expected: parseExpected(expected, `${path10}.expected`)
|
|
6243
|
+
};
|
|
6244
|
+
}
|
|
6245
|
+
function parseGoldenDataset(raw, sourceLabel) {
|
|
6246
|
+
if (!isRecord(raw)) {
|
|
6247
|
+
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
6248
|
+
}
|
|
6249
|
+
const version = raw.version;
|
|
6250
|
+
const name = raw.name;
|
|
6251
|
+
const description = raw.description;
|
|
6252
|
+
const queriesRaw = raw.queries;
|
|
6253
|
+
if (typeof version !== "string" || version.trim().length === 0) {
|
|
6254
|
+
throw new Error(`${sourceLabel}.version must be a non-empty string`);
|
|
6255
|
+
}
|
|
6256
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
6257
|
+
throw new Error(`${sourceLabel}.name must be a non-empty string`);
|
|
6258
|
+
}
|
|
6259
|
+
if (description !== void 0 && typeof description !== "string") {
|
|
6260
|
+
throw new Error(`${sourceLabel}.description must be a string when provided`);
|
|
6261
|
+
}
|
|
6262
|
+
if (!Array.isArray(queriesRaw)) {
|
|
6263
|
+
throw new Error(`${sourceLabel}.queries must be an array`);
|
|
6264
|
+
}
|
|
6265
|
+
if (queriesRaw.length === 0) {
|
|
6266
|
+
throw new Error(`${sourceLabel}.queries must contain at least one query`);
|
|
6267
|
+
}
|
|
6268
|
+
const queries = queriesRaw.map((query, idx) => parseQuery(query, idx));
|
|
6269
|
+
const idSet = /* @__PURE__ */ new Set();
|
|
6270
|
+
for (const query of queries) {
|
|
6271
|
+
if (idSet.has(query.id)) {
|
|
6272
|
+
throw new Error(`${sourceLabel}.queries has duplicate id: ${query.id}`);
|
|
6273
|
+
}
|
|
6274
|
+
idSet.add(query.id);
|
|
6275
|
+
}
|
|
6276
|
+
return {
|
|
6277
|
+
version,
|
|
6278
|
+
name,
|
|
6279
|
+
description: typeof description === "string" ? description : void 0,
|
|
6280
|
+
queries
|
|
6281
|
+
};
|
|
6282
|
+
}
|
|
6283
|
+
function loadGoldenDataset(datasetPath) {
|
|
6284
|
+
const parsed = parseJsonFile(datasetPath);
|
|
6285
|
+
return parseGoldenDataset(parsed, datasetPath);
|
|
6286
|
+
}
|
|
6287
|
+
function parseBudget(raw, sourceLabel) {
|
|
6288
|
+
if (!isRecord(raw)) {
|
|
6289
|
+
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
6290
|
+
}
|
|
6291
|
+
const name = raw.name;
|
|
6292
|
+
const baselinePath = raw.baselinePath;
|
|
6293
|
+
const failOnMissingBaseline = raw.failOnMissingBaseline;
|
|
6294
|
+
const thresholds = raw.thresholds;
|
|
6295
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
6296
|
+
throw new Error(`${sourceLabel}.name must be a non-empty string`);
|
|
6297
|
+
}
|
|
6298
|
+
if (baselinePath !== void 0 && typeof baselinePath !== "string") {
|
|
6299
|
+
throw new Error(`${sourceLabel}.baselinePath must be a string when provided`);
|
|
6300
|
+
}
|
|
6301
|
+
if (!isRecord(thresholds)) {
|
|
6302
|
+
throw new Error(`${sourceLabel}.thresholds must be an object`);
|
|
6303
|
+
}
|
|
6304
|
+
return {
|
|
6305
|
+
name,
|
|
6306
|
+
baselinePath: typeof baselinePath === "string" ? baselinePath : void 0,
|
|
6307
|
+
failOnMissingBaseline: typeof failOnMissingBaseline === "boolean" ? failOnMissingBaseline : true,
|
|
6308
|
+
thresholds: {
|
|
6309
|
+
hitAt5MaxDrop: thresholds.hitAt5MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.hitAt5MaxDrop, `${sourceLabel}.thresholds.hitAt5MaxDrop`),
|
|
6310
|
+
mrrAt10MaxDrop: thresholds.mrrAt10MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.mrrAt10MaxDrop, `${sourceLabel}.thresholds.mrrAt10MaxDrop`),
|
|
6311
|
+
p95LatencyMaxMultiplier: thresholds.p95LatencyMaxMultiplier === void 0 ? void 0 : asPositiveNumber(
|
|
6312
|
+
thresholds.p95LatencyMaxMultiplier,
|
|
6313
|
+
`${sourceLabel}.thresholds.p95LatencyMaxMultiplier`
|
|
6314
|
+
),
|
|
6315
|
+
p95LatencyMaxAbsoluteMs: thresholds.p95LatencyMaxAbsoluteMs === void 0 ? void 0 : asPositiveNumber(
|
|
6316
|
+
thresholds.p95LatencyMaxAbsoluteMs,
|
|
6317
|
+
`${sourceLabel}.thresholds.p95LatencyMaxAbsoluteMs`
|
|
6318
|
+
),
|
|
6319
|
+
minHitAt5: thresholds.minHitAt5 === void 0 ? void 0 : asPositiveNumber(thresholds.minHitAt5, `${sourceLabel}.thresholds.minHitAt5`),
|
|
6320
|
+
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`)
|
|
6321
|
+
}
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
function loadBudget(budgetPath) {
|
|
6325
|
+
const parsed = parseJsonFile(budgetPath);
|
|
6326
|
+
return parseBudget(parsed, budgetPath);
|
|
6327
|
+
}
|
|
6328
|
+
|
|
6329
|
+
// src/eval/runner.ts
|
|
6330
|
+
function toAbsolute(projectRoot, maybeRelative) {
|
|
6331
|
+
return path7.isAbsolute(maybeRelative) ? maybeRelative : path7.join(projectRoot, maybeRelative);
|
|
6332
|
+
}
|
|
6333
|
+
function loadRawConfig(projectRoot, configPath) {
|
|
6334
|
+
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
6335
|
+
if (fromPath && existsSync5(fromPath)) {
|
|
6336
|
+
return JSON.parse(readFileSync7(fromPath, "utf-8"));
|
|
6337
|
+
}
|
|
6338
|
+
const projectConfig = path7.join(projectRoot, ".opencode", "codebase-index.json");
|
|
6339
|
+
if (existsSync5(projectConfig)) {
|
|
6340
|
+
return JSON.parse(readFileSync7(projectConfig, "utf-8"));
|
|
6341
|
+
}
|
|
6342
|
+
const globalConfig = path7.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
|
|
6343
|
+
if (existsSync5(globalConfig)) {
|
|
6344
|
+
return JSON.parse(readFileSync7(globalConfig, "utf-8"));
|
|
6345
|
+
}
|
|
6346
|
+
return {};
|
|
6347
|
+
}
|
|
6348
|
+
function getIndexRootPath(projectRoot, scope) {
|
|
6349
|
+
if (scope === "global") {
|
|
6350
|
+
return path7.join(os3.homedir(), ".opencode", "global-index");
|
|
6351
|
+
}
|
|
6352
|
+
return path7.join(projectRoot, ".opencode", "index");
|
|
6353
|
+
}
|
|
6354
|
+
function clearIndexRoot(projectRoot, scope) {
|
|
6355
|
+
const indexRoot = getIndexRootPath(projectRoot, scope);
|
|
6356
|
+
if (existsSync5(indexRoot)) {
|
|
6357
|
+
rmSync(indexRoot, { recursive: true, force: true });
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
function loadParsedConfig(projectRoot, configPath) {
|
|
6361
|
+
const raw = loadRawConfig(projectRoot, configPath);
|
|
6362
|
+
return parseConfig(raw);
|
|
6363
|
+
}
|
|
6364
|
+
function resolveSearchConfig(parsedConfig, overrides) {
|
|
6365
|
+
const nextSearch = {
|
|
6366
|
+
...parsedConfig.search
|
|
6367
|
+
};
|
|
6368
|
+
if (overrides?.fusionStrategy !== void 0) {
|
|
6369
|
+
nextSearch.fusionStrategy = overrides.fusionStrategy;
|
|
6370
|
+
}
|
|
6371
|
+
if (overrides?.hybridWeight !== void 0) {
|
|
6372
|
+
nextSearch.hybridWeight = overrides.hybridWeight;
|
|
6373
|
+
}
|
|
6374
|
+
if (overrides?.rrfK !== void 0) {
|
|
6375
|
+
nextSearch.rrfK = overrides.rrfK;
|
|
6376
|
+
}
|
|
6377
|
+
if (overrides?.rerankTopN !== void 0) {
|
|
6378
|
+
nextSearch.rerankTopN = overrides.rerankTopN;
|
|
6379
|
+
}
|
|
6380
|
+
return {
|
|
6381
|
+
...parsedConfig,
|
|
6382
|
+
search: nextSearch
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
6385
|
+
async function runEvaluation(options) {
|
|
6386
|
+
const datasetPath = toAbsolute(options.projectRoot, options.datasetPath);
|
|
6387
|
+
const againstPath = options.againstPath ? toAbsolute(options.projectRoot, options.againstPath) : void 0;
|
|
6388
|
+
const budgetPath = options.budgetPath ? toAbsolute(options.projectRoot, options.budgetPath) : void 0;
|
|
6389
|
+
const dataset = loadGoldenDataset(datasetPath);
|
|
6390
|
+
const parsedConfig = loadParsedConfig(options.projectRoot, options.configPath);
|
|
6391
|
+
const effectiveConfig = resolveSearchConfig(parsedConfig, options.searchOverrides);
|
|
6392
|
+
if (options.reindex) {
|
|
6393
|
+
clearIndexRoot(options.projectRoot, effectiveConfig.scope);
|
|
6394
|
+
}
|
|
6395
|
+
const indexer = new Indexer(options.projectRoot, effectiveConfig);
|
|
6396
|
+
await indexer.index();
|
|
6397
|
+
const perQuery = [];
|
|
6398
|
+
for (const query of dataset.queries) {
|
|
6399
|
+
if (query.expected.branch && query.expected.branch !== indexer.getCurrentBranch()) {
|
|
6400
|
+
throw new Error(
|
|
6401
|
+
`Query '${query.id}' expects branch '${query.expected.branch}', but current branch is '${indexer.getCurrentBranch()}'. Switch branch before running this dataset.`
|
|
6402
|
+
);
|
|
6403
|
+
}
|
|
6404
|
+
const start = performance3.now();
|
|
6405
|
+
const result = await indexer.search(query.query, 10, {
|
|
6406
|
+
metadataOnly: true,
|
|
6407
|
+
filterByBranch: query.expected.branch ? true : false
|
|
6408
|
+
});
|
|
6409
|
+
const elapsed = performance3.now() - start;
|
|
6410
|
+
const materialized = result.map((item) => ({
|
|
6411
|
+
filePath: item.filePath,
|
|
6412
|
+
startLine: item.startLine,
|
|
6413
|
+
endLine: item.endLine,
|
|
6414
|
+
score: item.score,
|
|
6415
|
+
chunkType: item.chunkType,
|
|
6416
|
+
name: item.name
|
|
6417
|
+
}));
|
|
6418
|
+
perQuery.push(buildPerQueryResult(query, materialized, elapsed, 10));
|
|
6419
|
+
}
|
|
6420
|
+
const logger = indexer.getLogger();
|
|
6421
|
+
const metricSnapshot = logger.getMetrics();
|
|
6422
|
+
const costPer1MTokensUsd = effectiveConfig.embeddingProvider === "custom" || effectiveConfig.embeddingProvider === "auto" ? 0 : getDefaultModelForProvider(effectiveConfig.embeddingProvider).costPer1MTokens;
|
|
6423
|
+
const summary = {
|
|
6424
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6425
|
+
projectRoot: options.projectRoot,
|
|
6426
|
+
datasetPath,
|
|
6427
|
+
datasetName: dataset.name,
|
|
6428
|
+
datasetVersion: dataset.version,
|
|
6429
|
+
queryCount: dataset.queries.length,
|
|
6430
|
+
topK: 10,
|
|
6431
|
+
searchConfig: {
|
|
6432
|
+
fusionStrategy: effectiveConfig.search.fusionStrategy,
|
|
6433
|
+
hybridWeight: effectiveConfig.search.hybridWeight,
|
|
6434
|
+
rrfK: effectiveConfig.search.rrfK,
|
|
6435
|
+
rerankTopN: effectiveConfig.search.rerankTopN
|
|
6436
|
+
},
|
|
6437
|
+
metrics: computeEvalMetrics(
|
|
6438
|
+
dataset.queries,
|
|
6439
|
+
perQuery,
|
|
6440
|
+
metricSnapshot.embeddingApiCalls,
|
|
6441
|
+
metricSnapshot.embeddingTokensUsed,
|
|
6442
|
+
costPer1MTokensUsd
|
|
6443
|
+
)
|
|
6444
|
+
};
|
|
6445
|
+
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
6446
|
+
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
6447
|
+
writeJson(path7.join(outputDir, "summary.json"), summary);
|
|
6448
|
+
writeJson(path7.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
6449
|
+
let comparison;
|
|
6450
|
+
if (againstPath) {
|
|
6451
|
+
const baseline = loadSummary(againstPath);
|
|
6452
|
+
comparison = compareSummaries(summary, baseline, againstPath);
|
|
6453
|
+
writeJson(path7.join(outputDir, "compare.json"), comparison);
|
|
6454
|
+
}
|
|
6455
|
+
let gate;
|
|
6456
|
+
if (options.ciMode) {
|
|
6457
|
+
if (!budgetPath) {
|
|
6458
|
+
throw new Error("CI mode requires --budget path");
|
|
6459
|
+
}
|
|
6460
|
+
const budget = loadBudget(budgetPath);
|
|
6461
|
+
if (!comparison && budget.baselinePath) {
|
|
6462
|
+
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
6463
|
+
if (existsSync5(resolvedBaseline)) {
|
|
6464
|
+
const baselineSummary = loadSummary(resolvedBaseline);
|
|
6465
|
+
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
6466
|
+
writeJson(path7.join(outputDir, "compare.json"), comparison);
|
|
6467
|
+
} else if (budget.failOnMissingBaseline) {
|
|
6468
|
+
throw new Error(
|
|
6469
|
+
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
6470
|
+
);
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
6474
|
+
}
|
|
6475
|
+
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
6476
|
+
writeText(path7.join(outputDir, "summary.md"), markdown);
|
|
6477
|
+
return { outputDir, summary, perQuery, comparison, gate };
|
|
6478
|
+
}
|
|
6479
|
+
async function runSweep(options, sweep) {
|
|
6480
|
+
const fusionValues = sweep.fusionStrategy && sweep.fusionStrategy.length > 0 ? [...sweep.fusionStrategy] : [void 0];
|
|
6481
|
+
const weightValues = sweep.hybridWeight && sweep.hybridWeight.length > 0 ? [...sweep.hybridWeight] : [void 0];
|
|
6482
|
+
const rrfValues = sweep.rrfK && sweep.rrfK.length > 0 ? [...sweep.rrfK] : [void 0];
|
|
6483
|
+
const rerankValues = sweep.rerankTopN && sweep.rerankTopN.length > 0 ? [...sweep.rerankTopN] : [void 0];
|
|
6484
|
+
const runs = [];
|
|
6485
|
+
for (const fusion of fusionValues) {
|
|
6486
|
+
for (const hybridWeight of weightValues) {
|
|
6487
|
+
for (const rrfK of rrfValues) {
|
|
6488
|
+
for (const rerankTopN of rerankValues) {
|
|
6489
|
+
const run = await runEvaluation({
|
|
6490
|
+
...options,
|
|
6491
|
+
searchOverrides: {
|
|
6492
|
+
...fusion !== void 0 ? { fusionStrategy: fusion } : {},
|
|
6493
|
+
...hybridWeight !== void 0 ? { hybridWeight } : {},
|
|
6494
|
+
...rrfK !== void 0 ? { rrfK } : {},
|
|
6495
|
+
...rerankTopN !== void 0 ? { rerankTopN } : {}
|
|
6496
|
+
}
|
|
6497
|
+
});
|
|
6498
|
+
runs.push({
|
|
6499
|
+
searchConfig: run.summary.searchConfig,
|
|
6500
|
+
summary: run.summary,
|
|
6501
|
+
comparison: run.comparison,
|
|
6502
|
+
gate: run.gate
|
|
6503
|
+
});
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
const bestByHitAt5 = [...runs].sort(
|
|
6509
|
+
(a, b) => b.summary.metrics.hitAt5 - a.summary.metrics.hitAt5
|
|
6510
|
+
)[0];
|
|
6511
|
+
const bestByMrrAt10 = [...runs].sort(
|
|
6512
|
+
(a, b) => b.summary.metrics.mrrAt10 - a.summary.metrics.mrrAt10
|
|
6513
|
+
)[0];
|
|
6514
|
+
const bestByP95Latency = [...runs].sort(
|
|
6515
|
+
(a, b) => a.summary.metrics.latencyMs.p95 - b.summary.metrics.latencyMs.p95
|
|
6516
|
+
)[0];
|
|
6517
|
+
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
6518
|
+
const failedGateRuns = runs.filter((run) => run.gate && !run.gate.passed).length;
|
|
6519
|
+
const gatePassed = failedGateRuns === 0;
|
|
6520
|
+
const aggregate = {
|
|
6521
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6522
|
+
againstPath: options.againstPath,
|
|
6523
|
+
runCount: runs.length,
|
|
6524
|
+
runs,
|
|
6525
|
+
gatePassed,
|
|
6526
|
+
failedGateRuns,
|
|
6527
|
+
bestByHitAt5,
|
|
6528
|
+
bestByMrrAt10,
|
|
6529
|
+
bestByP95Latency
|
|
6530
|
+
};
|
|
6531
|
+
writeJson(path7.join(outputDir, "compare.json"), aggregate);
|
|
6532
|
+
const md = createSummaryMarkdown(
|
|
6533
|
+
bestByHitAt5?.summary ?? runs[0].summary,
|
|
6534
|
+
bestByHitAt5?.comparison,
|
|
6535
|
+
void 0,
|
|
6536
|
+
aggregate
|
|
6537
|
+
);
|
|
6538
|
+
writeText(path7.join(outputDir, "summary.md"), md);
|
|
6539
|
+
writeJson(path7.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
6540
|
+
return { outputDir, aggregate };
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
// src/eval/cli.ts
|
|
6544
|
+
function printUsage() {
|
|
6545
|
+
console.log(`
|
|
6546
|
+
Usage:
|
|
6547
|
+
opencode-codebase-index-mcp eval run [options]
|
|
6548
|
+
opencode-codebase-index-mcp eval compare --against <summary.json> [options]
|
|
6549
|
+
opencode-codebase-index-mcp eval diff --current <summary.json> --against <summary.json> [options]
|
|
6550
|
+
|
|
6551
|
+
Options:
|
|
6552
|
+
--project <path> Project root (default: cwd)
|
|
6553
|
+
--config <path> Config JSON path
|
|
6554
|
+
--dataset <path> Golden dataset path (default: benchmarks/golden/small.json)
|
|
6555
|
+
--current <path> Current summary.json path (required for eval diff)
|
|
6556
|
+
--output <path> Output root dir (default: benchmarks/results)
|
|
6557
|
+
--against <path> Baseline summary.json to compare against
|
|
6558
|
+
--budget <path> Budget file for CI mode (default: benchmarks/budgets/default.json)
|
|
6559
|
+
--ci Enable CI gate mode
|
|
6560
|
+
--reindex Force reindex before eval
|
|
6561
|
+
|
|
6562
|
+
Search overrides:
|
|
6563
|
+
--fusionStrategy <rrf|weighted>
|
|
6564
|
+
--hybridWeight <0-1>
|
|
6565
|
+
--rrfK <number>
|
|
6566
|
+
--rerankTopN <number>
|
|
6567
|
+
|
|
6568
|
+
Sweep options (comma-separated values):
|
|
6569
|
+
--sweepFusionStrategy <rrf,weighted>
|
|
6570
|
+
--sweepHybridWeight <0.3,0.5,0.7>
|
|
6571
|
+
--sweepRrfK <30,60,90>
|
|
6572
|
+
--sweepRerankTopN <10,20,40>
|
|
6573
|
+
`);
|
|
6574
|
+
}
|
|
6575
|
+
function parseNumber(value, flag) {
|
|
6576
|
+
const parsed = Number(value);
|
|
6577
|
+
if (Number.isNaN(parsed)) {
|
|
6578
|
+
throw new Error(`${flag} must be a number`);
|
|
6579
|
+
}
|
|
6580
|
+
return parsed;
|
|
6581
|
+
}
|
|
6582
|
+
function parseCsvNumbers(value, flag) {
|
|
6583
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0).map((item) => parseNumber(item, flag));
|
|
6584
|
+
}
|
|
6585
|
+
function parseCsvFusion(value) {
|
|
6586
|
+
const values = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6587
|
+
const parsed = [];
|
|
6588
|
+
for (const candidate of values) {
|
|
6589
|
+
if (candidate !== "rrf" && candidate !== "weighted") {
|
|
6590
|
+
throw new Error("--sweepFusionStrategy accepts only rrf,weighted");
|
|
6591
|
+
}
|
|
6592
|
+
parsed.push(candidate);
|
|
6593
|
+
}
|
|
6594
|
+
return parsed;
|
|
6595
|
+
}
|
|
6596
|
+
function hasSweepOptions(sweep) {
|
|
6597
|
+
return Boolean(
|
|
6598
|
+
sweep.fusionStrategy && sweep.fusionStrategy.length > 0 || sweep.hybridWeight && sweep.hybridWeight.length > 0 || sweep.rrfK && sweep.rrfK.length > 0 || sweep.rerankTopN && sweep.rerankTopN.length > 0
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
function parseEvalArgs(argv, cwd) {
|
|
6602
|
+
const parsed = {
|
|
6603
|
+
projectRoot: cwd,
|
|
6604
|
+
datasetPath: "benchmarks/golden/small.json",
|
|
6605
|
+
outputRoot: "benchmarks/results",
|
|
6606
|
+
budgetPath: "benchmarks/budgets/default.json",
|
|
6607
|
+
ciMode: false,
|
|
6608
|
+
reindex: false,
|
|
6609
|
+
sweep: {}
|
|
6610
|
+
};
|
|
6611
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6612
|
+
const arg = argv[i];
|
|
6613
|
+
const next = argv[i + 1];
|
|
6614
|
+
if (arg === "--project" && next) {
|
|
6615
|
+
parsed.projectRoot = path8.resolve(cwd, next);
|
|
6616
|
+
i += 1;
|
|
6617
|
+
continue;
|
|
6618
|
+
}
|
|
6619
|
+
if (arg === "--config" && next) {
|
|
6620
|
+
parsed.configPath = path8.resolve(cwd, next);
|
|
6621
|
+
i += 1;
|
|
6622
|
+
continue;
|
|
6623
|
+
}
|
|
6624
|
+
if (arg === "--dataset" && next) {
|
|
6625
|
+
parsed.datasetPath = next;
|
|
6626
|
+
i += 1;
|
|
6627
|
+
continue;
|
|
6628
|
+
}
|
|
6629
|
+
if (arg === "--current" && next) {
|
|
6630
|
+
parsed.currentPath = next;
|
|
6631
|
+
i += 1;
|
|
6632
|
+
continue;
|
|
6633
|
+
}
|
|
6634
|
+
if (arg === "--output" && next) {
|
|
6635
|
+
parsed.outputRoot = next;
|
|
6636
|
+
i += 1;
|
|
6637
|
+
continue;
|
|
6638
|
+
}
|
|
6639
|
+
if (arg === "--against" && next) {
|
|
6640
|
+
parsed.againstPath = next;
|
|
6641
|
+
i += 1;
|
|
6642
|
+
continue;
|
|
6643
|
+
}
|
|
6644
|
+
if (arg === "--budget" && next) {
|
|
6645
|
+
parsed.budgetPath = next;
|
|
6646
|
+
i += 1;
|
|
6647
|
+
continue;
|
|
6648
|
+
}
|
|
6649
|
+
if (arg === "--ci") {
|
|
6650
|
+
parsed.ciMode = true;
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
if (arg === "--reindex") {
|
|
6654
|
+
parsed.reindex = true;
|
|
6655
|
+
continue;
|
|
6656
|
+
}
|
|
6657
|
+
if (arg === "--fusionStrategy" && next) {
|
|
6658
|
+
if (next !== "rrf" && next !== "weighted") {
|
|
6659
|
+
throw new Error("--fusionStrategy must be rrf or weighted");
|
|
6660
|
+
}
|
|
6661
|
+
parsed.fusionStrategy = next;
|
|
6662
|
+
i += 1;
|
|
6663
|
+
continue;
|
|
6664
|
+
}
|
|
6665
|
+
if (arg === "--hybridWeight" && next) {
|
|
6666
|
+
parsed.hybridWeight = parseNumber(next, "--hybridWeight");
|
|
6667
|
+
i += 1;
|
|
6668
|
+
continue;
|
|
6669
|
+
}
|
|
6670
|
+
if (arg === "--rrfK" && next) {
|
|
6671
|
+
parsed.rrfK = parseNumber(next, "--rrfK");
|
|
6672
|
+
i += 1;
|
|
6673
|
+
continue;
|
|
6674
|
+
}
|
|
6675
|
+
if (arg === "--rerankTopN" && next) {
|
|
6676
|
+
parsed.rerankTopN = parseNumber(next, "--rerankTopN");
|
|
6677
|
+
i += 1;
|
|
6678
|
+
continue;
|
|
6679
|
+
}
|
|
6680
|
+
if (arg === "--sweepFusionStrategy" && next) {
|
|
6681
|
+
parsed.sweep.fusionStrategy = parseCsvFusion(next);
|
|
6682
|
+
i += 1;
|
|
6683
|
+
continue;
|
|
6684
|
+
}
|
|
6685
|
+
if (arg === "--sweepHybridWeight" && next) {
|
|
6686
|
+
parsed.sweep.hybridWeight = parseCsvNumbers(next, "--sweepHybridWeight");
|
|
6687
|
+
i += 1;
|
|
6688
|
+
continue;
|
|
6689
|
+
}
|
|
6690
|
+
if (arg === "--sweepRrfK" && next) {
|
|
6691
|
+
parsed.sweep.rrfK = parseCsvNumbers(next, "--sweepRrfK");
|
|
6692
|
+
i += 1;
|
|
6693
|
+
continue;
|
|
6694
|
+
}
|
|
6695
|
+
if (arg === "--sweepRerankTopN" && next) {
|
|
6696
|
+
parsed.sweep.rerankTopN = parseCsvNumbers(next, "--sweepRerankTopN");
|
|
6697
|
+
i += 1;
|
|
6698
|
+
continue;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
return parsed;
|
|
6702
|
+
}
|
|
6703
|
+
function parseEvalSubcommandOptions(argv, cwd) {
|
|
6704
|
+
let explicitAgainst;
|
|
6705
|
+
const filtered = [];
|
|
6706
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6707
|
+
const current = argv[i];
|
|
6708
|
+
const next = argv[i + 1];
|
|
6709
|
+
if (current === "--against" && next) {
|
|
6710
|
+
explicitAgainst = next;
|
|
6711
|
+
i += 1;
|
|
6712
|
+
continue;
|
|
6713
|
+
}
|
|
6714
|
+
filtered.push(current);
|
|
6715
|
+
}
|
|
6716
|
+
return {
|
|
6717
|
+
parsed: parseEvalArgs(filtered, cwd),
|
|
6718
|
+
explicitAgainst
|
|
6719
|
+
};
|
|
6720
|
+
}
|
|
6721
|
+
function toRunOptions(parsed) {
|
|
6722
|
+
return {
|
|
6723
|
+
projectRoot: parsed.projectRoot,
|
|
6724
|
+
configPath: parsed.configPath,
|
|
6725
|
+
datasetPath: parsed.datasetPath,
|
|
6726
|
+
outputRoot: parsed.outputRoot,
|
|
6727
|
+
againstPath: parsed.againstPath,
|
|
6728
|
+
budgetPath: parsed.budgetPath,
|
|
6729
|
+
ciMode: parsed.ciMode,
|
|
6730
|
+
reindex: parsed.reindex,
|
|
6731
|
+
searchOverrides: {
|
|
6732
|
+
...parsed.fusionStrategy !== void 0 ? { fusionStrategy: parsed.fusionStrategy } : {},
|
|
6733
|
+
...parsed.hybridWeight !== void 0 ? { hybridWeight: parsed.hybridWeight } : {},
|
|
6734
|
+
...parsed.rrfK !== void 0 ? { rrfK: parsed.rrfK } : {},
|
|
6735
|
+
...parsed.rerankTopN !== void 0 ? { rerankTopN: parsed.rerankTopN } : {}
|
|
6736
|
+
}
|
|
6737
|
+
};
|
|
6738
|
+
}
|
|
6739
|
+
async function handleEvalCommand(args, cwd) {
|
|
6740
|
+
const subcommand = args[0];
|
|
6741
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
6742
|
+
printUsage();
|
|
6743
|
+
return 0;
|
|
6744
|
+
}
|
|
6745
|
+
if (subcommand === "run") {
|
|
6746
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6747
|
+
if (explicitAgainst) {
|
|
6748
|
+
parsed.againstPath = explicitAgainst;
|
|
6749
|
+
}
|
|
6750
|
+
const runOptions = toRunOptions(parsed);
|
|
6751
|
+
if (hasSweepOptions(parsed.sweep)) {
|
|
6752
|
+
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
6753
|
+
console.log(`Eval sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
6754
|
+
console.log(`Sweep runs: ${sweep.aggregate.runCount}`);
|
|
6755
|
+
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
6756
|
+
console.error(
|
|
6757
|
+
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
6758
|
+
);
|
|
6759
|
+
return 1;
|
|
6760
|
+
}
|
|
6761
|
+
return 0;
|
|
6762
|
+
}
|
|
6763
|
+
const result = await runEvaluation(runOptions);
|
|
6764
|
+
console.log(`Eval run complete. Artifacts: ${result.outputDir}`);
|
|
6765
|
+
console.log(
|
|
6766
|
+
`Hit@5=${(result.summary.metrics.hitAt5 * 100).toFixed(2)}% MRR@10=${result.summary.metrics.mrrAt10.toFixed(4)} p95=${result.summary.metrics.latencyMs.p95.toFixed(3)}ms`
|
|
6767
|
+
);
|
|
6768
|
+
if (result.gate && !result.gate.passed) {
|
|
6769
|
+
for (const violation of result.gate.violations) {
|
|
6770
|
+
console.error(`[CI-GATE] ${violation.metric}: ${violation.message}`);
|
|
6771
|
+
}
|
|
6772
|
+
return 1;
|
|
6773
|
+
}
|
|
6774
|
+
return 0;
|
|
6775
|
+
}
|
|
6776
|
+
if (subcommand === "compare") {
|
|
6777
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6778
|
+
if (!explicitAgainst) {
|
|
6779
|
+
throw new Error("eval compare requires --against <baseline summary.json>");
|
|
6780
|
+
}
|
|
6781
|
+
parsed.againstPath = explicitAgainst;
|
|
6782
|
+
const runOptions = toRunOptions(parsed);
|
|
6783
|
+
if (hasSweepOptions(parsed.sweep)) {
|
|
6784
|
+
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
6785
|
+
console.log(`Eval compare sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
6786
|
+
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
6787
|
+
console.error(
|
|
6788
|
+
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
6789
|
+
);
|
|
6790
|
+
return 1;
|
|
6791
|
+
}
|
|
6792
|
+
return 0;
|
|
6793
|
+
}
|
|
6794
|
+
const result = await runEvaluation(runOptions);
|
|
6795
|
+
console.log(`Eval compare complete. Artifacts: ${result.outputDir}`);
|
|
6796
|
+
return 0;
|
|
6797
|
+
}
|
|
6798
|
+
if (subcommand === "diff") {
|
|
6799
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6800
|
+
if (!explicitAgainst) {
|
|
6801
|
+
throw new Error("eval diff requires --against <baseline summary.json>");
|
|
6802
|
+
}
|
|
6803
|
+
if (!parsed.currentPath) {
|
|
6804
|
+
throw new Error("eval diff requires --current <current summary.json>");
|
|
6805
|
+
}
|
|
6806
|
+
parsed.againstPath = explicitAgainst;
|
|
6807
|
+
const currentPath = parsed.currentPath;
|
|
6808
|
+
if (!currentPath.endsWith(".json")) {
|
|
6809
|
+
throw new Error("eval diff --current must point to a summary JSON file");
|
|
6810
|
+
}
|
|
6811
|
+
if (!parsed.againstPath.endsWith(".json")) {
|
|
6812
|
+
throw new Error("eval diff --against must point to a summary JSON file");
|
|
6813
|
+
}
|
|
6814
|
+
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath));
|
|
6815
|
+
const baselineSummary = loadSummary(path8.resolve(parsed.projectRoot, parsed.againstPath));
|
|
6816
|
+
const comparison = compareSummaries(
|
|
6817
|
+
currentSummary,
|
|
6818
|
+
baselineSummary,
|
|
6819
|
+
path8.resolve(parsed.projectRoot, parsed.againstPath)
|
|
6820
|
+
);
|
|
6821
|
+
const outputDir = createRunDirectory(path8.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
6822
|
+
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
6823
|
+
writeJson(path8.join(outputDir, "compare.json"), comparison);
|
|
6824
|
+
writeText(path8.join(outputDir, "summary.md"), summaryMd);
|
|
6825
|
+
writeJson(path8.join(outputDir, "summary.json"), currentSummary);
|
|
6826
|
+
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
6827
|
+
return 0;
|
|
6828
|
+
}
|
|
6829
|
+
throw new Error(`Unknown eval subcommand: ${subcommand}`);
|
|
6830
|
+
}
|
|
6831
|
+
|
|
4594
6832
|
// src/mcp-server.ts
|
|
6833
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6834
|
+
import { z } from "zod";
|
|
6835
|
+
|
|
6836
|
+
// src/tools/utils.ts
|
|
4595
6837
|
var MAX_CONTENT_LINES = 30;
|
|
4596
6838
|
function truncateContent(content) {
|
|
4597
6839
|
const lines = content.split("\n");
|
|
@@ -4599,6 +6841,31 @@ function truncateContent(content) {
|
|
|
4599
6841
|
return lines.slice(0, MAX_CONTENT_LINES).join("\n") + `
|
|
4600
6842
|
// ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
|
|
4601
6843
|
}
|
|
6844
|
+
function formatDefinitionLookup(results, query) {
|
|
6845
|
+
if (results.length === 0) {
|
|
6846
|
+
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
6847
|
+
}
|
|
6848
|
+
const formatted = results.map((r, idx) => {
|
|
6849
|
+
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}`;
|
|
6850
|
+
return `${header2} (score: ${r.score.toFixed(2)})
|
|
6851
|
+
\`\`\`
|
|
6852
|
+
${truncateContent(r.content)}
|
|
6853
|
+
\`\`\``;
|
|
6854
|
+
});
|
|
6855
|
+
const header = results.length === 1 ? `Definition found for "${query}":` : `Found ${results.length} definition candidates for "${query}":`;
|
|
6856
|
+
return `${header}
|
|
6857
|
+
|
|
6858
|
+
${formatted.join("\n\n")}`;
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
// src/mcp-server.ts
|
|
6862
|
+
var MAX_CONTENT_LINES2 = 30;
|
|
6863
|
+
function truncateContent2(content) {
|
|
6864
|
+
const lines = content.split("\n");
|
|
6865
|
+
if (lines.length <= MAX_CONTENT_LINES2) return content;
|
|
6866
|
+
return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
|
|
6867
|
+
// ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
|
|
6868
|
+
}
|
|
4602
6869
|
function formatIndexStats(stats, verbose = false) {
|
|
4603
6870
|
const lines = [];
|
|
4604
6871
|
if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
|
|
@@ -4712,7 +6979,7 @@ function createMcpServer(projectRoot, config) {
|
|
|
4712
6979
|
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}`;
|
|
4713
6980
|
return `${header} (score: ${r.score.toFixed(2)})
|
|
4714
6981
|
\`\`\`
|
|
4715
|
-
${
|
|
6982
|
+
${truncateContent2(r.content)}
|
|
4716
6983
|
\`\`\``;
|
|
4717
6984
|
});
|
|
4718
6985
|
return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
|
|
@@ -4791,7 +7058,7 @@ Use Read tool to examine specific files.` }] };
|
|
|
4791
7058
|
async () => {
|
|
4792
7059
|
await ensureInitialized();
|
|
4793
7060
|
const result = await indexer.healthCheck();
|
|
4794
|
-
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
|
|
7061
|
+
if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
|
|
4795
7062
|
return { content: [{ type: "text", text: "Index is healthy. No stale entries found." }] };
|
|
4796
7063
|
}
|
|
4797
7064
|
const lines = [`Health check complete:`];
|
|
@@ -4804,6 +7071,12 @@ Use Read tool to examine specific files.` }] };
|
|
|
4804
7071
|
if (result.gcOrphanChunks > 0) {
|
|
4805
7072
|
lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
|
|
4806
7073
|
}
|
|
7074
|
+
if (result.gcOrphanSymbols > 0) {
|
|
7075
|
+
lines.push(` Garbage collected orphan symbols: ${result.gcOrphanSymbols}`);
|
|
7076
|
+
}
|
|
7077
|
+
if (result.gcOrphanCallEdges > 0) {
|
|
7078
|
+
lines.push(` Garbage collected orphan call edges: ${result.gcOrphanCallEdges}`);
|
|
7079
|
+
}
|
|
4807
7080
|
if (result.filePaths.length > 0) {
|
|
4808
7081
|
lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
|
|
4809
7082
|
}
|
|
@@ -4884,7 +7157,7 @@ Use Read tool to examine specific files.` }] };
|
|
|
4884
7157
|
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}`;
|
|
4885
7158
|
return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
|
|
4886
7159
|
\`\`\`
|
|
4887
|
-
${
|
|
7160
|
+
${truncateContent2(r.content)}
|
|
4888
7161
|
\`\`\``;
|
|
4889
7162
|
});
|
|
4890
7163
|
return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
|
|
@@ -4892,6 +7165,62 @@ ${truncateContent(r.content)}
|
|
|
4892
7165
|
${formatted.join("\n\n")}` }] };
|
|
4893
7166
|
}
|
|
4894
7167
|
);
|
|
7168
|
+
server.tool(
|
|
7169
|
+
"implementation_lookup",
|
|
7170
|
+
"Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s). Prefers real implementation files over tests, docs, examples, and fixtures.",
|
|
7171
|
+
{
|
|
7172
|
+
query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
7173
|
+
limit: z.number().optional().default(5).describe("Maximum number of results"),
|
|
7174
|
+
fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
7175
|
+
directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
7176
|
+
},
|
|
7177
|
+
async (args) => {
|
|
7178
|
+
await ensureInitialized();
|
|
7179
|
+
const results = await indexer.search(args.query, args.limit ?? 5, {
|
|
7180
|
+
fileType: args.fileType,
|
|
7181
|
+
directory: args.directory,
|
|
7182
|
+
definitionIntent: true
|
|
7183
|
+
});
|
|
7184
|
+
return { content: [{ type: "text", text: formatDefinitionLookup(results, args.query) }] };
|
|
7185
|
+
}
|
|
7186
|
+
);
|
|
7187
|
+
server.tool(
|
|
7188
|
+
"call_graph",
|
|
7189
|
+
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
|
|
7190
|
+
{
|
|
7191
|
+
name: z.string().describe("Function or method name to query"),
|
|
7192
|
+
direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
7193
|
+
symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction)")
|
|
7194
|
+
},
|
|
7195
|
+
async (args) => {
|
|
7196
|
+
await ensureInitialized();
|
|
7197
|
+
if (args.direction === "callees") {
|
|
7198
|
+
if (!args.symbolId) {
|
|
7199
|
+
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
7200
|
+
}
|
|
7201
|
+
const callees = await indexer.getCallees(args.symbolId);
|
|
7202
|
+
if (callees.length === 0) {
|
|
7203
|
+
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}.` }] };
|
|
7204
|
+
}
|
|
7205
|
+
const formatted2 = callees.map(
|
|
7206
|
+
(e, i) => `[${i + 1}] \u2192 ${e.targetName} (${e.callType}) at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`
|
|
7207
|
+
);
|
|
7208
|
+
return { content: [{ type: "text", text: `Callees (${callees.length}):
|
|
7209
|
+
|
|
7210
|
+
${formatted2.join("\n")}` }] };
|
|
7211
|
+
}
|
|
7212
|
+
const callers = await indexer.getCallers(args.name);
|
|
7213
|
+
if (callers.length === 0) {
|
|
7214
|
+
return { content: [{ type: "text", text: `No callers found for "${args.name}".` }] };
|
|
7215
|
+
}
|
|
7216
|
+
const formatted = callers.map(
|
|
7217
|
+
(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]"}`
|
|
7218
|
+
);
|
|
7219
|
+
return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
|
|
7220
|
+
|
|
7221
|
+
${formatted.join("\n")}` }] };
|
|
7222
|
+
}
|
|
7223
|
+
);
|
|
4895
7224
|
server.prompt(
|
|
4896
7225
|
"search",
|
|
4897
7226
|
"Search codebase by meaning using semantic search",
|
|
@@ -4961,14 +7290,30 @@ Use a hybrid approach:
|
|
|
4961
7290
|
}]
|
|
4962
7291
|
})
|
|
4963
7292
|
);
|
|
7293
|
+
server.prompt(
|
|
7294
|
+
"definition",
|
|
7295
|
+
"Find where a symbol is defined in the codebase",
|
|
7296
|
+
{ query: z.string().describe("Symbol name or description to find the definition of") },
|
|
7297
|
+
(args) => ({
|
|
7298
|
+
messages: [{
|
|
7299
|
+
role: "user",
|
|
7300
|
+
content: {
|
|
7301
|
+
type: "text",
|
|
7302
|
+
text: `Find the definition of: "${args.query}"
|
|
7303
|
+
|
|
7304
|
+
Use the implementation_lookup tool to find where this symbol is defined. This prioritizes real implementation files over tests, docs, and examples. If no definition is found, fall back to codebase_search for broader discovery.`
|
|
7305
|
+
}
|
|
7306
|
+
}]
|
|
7307
|
+
})
|
|
7308
|
+
);
|
|
4964
7309
|
return server;
|
|
4965
7310
|
}
|
|
4966
7311
|
|
|
4967
7312
|
// src/cli.ts
|
|
4968
7313
|
function loadJsonFile(filePath) {
|
|
4969
7314
|
try {
|
|
4970
|
-
if (
|
|
4971
|
-
const content =
|
|
7315
|
+
if (existsSync6(filePath)) {
|
|
7316
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
4972
7317
|
return JSON.parse(content);
|
|
4973
7318
|
}
|
|
4974
7319
|
} catch {
|
|
@@ -4980,9 +7325,9 @@ function loadPluginConfig(projectRoot, configPath) {
|
|
|
4980
7325
|
const config = loadJsonFile(configPath);
|
|
4981
7326
|
if (config) return config;
|
|
4982
7327
|
}
|
|
4983
|
-
const projectConfig = loadJsonFile(
|
|
7328
|
+
const projectConfig = loadJsonFile(path9.join(projectRoot, ".opencode", "codebase-index.json"));
|
|
4984
7329
|
if (projectConfig) return projectConfig;
|
|
4985
|
-
const globalConfig = loadJsonFile(
|
|
7330
|
+
const globalConfig = loadJsonFile(path9.join(os4.homedir(), ".config", "opencode", "codebase-index.json"));
|
|
4986
7331
|
if (globalConfig) return globalConfig;
|
|
4987
7332
|
return {};
|
|
4988
7333
|
}
|
|
@@ -4991,14 +7336,18 @@ function parseArgs(argv) {
|
|
|
4991
7336
|
let config;
|
|
4992
7337
|
for (let i = 2; i < argv.length; i++) {
|
|
4993
7338
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
4994
|
-
project =
|
|
7339
|
+
project = path9.resolve(argv[++i]);
|
|
4995
7340
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
4996
|
-
config =
|
|
7341
|
+
config = path9.resolve(argv[++i]);
|
|
4997
7342
|
}
|
|
4998
7343
|
}
|
|
4999
7344
|
return { project, config };
|
|
5000
7345
|
}
|
|
5001
7346
|
async function main() {
|
|
7347
|
+
if (process.argv[2] === "eval") {
|
|
7348
|
+
const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
|
|
7349
|
+
process.exit(exitCode);
|
|
7350
|
+
}
|
|
5002
7351
|
const args = parseArgs(process.argv);
|
|
5003
7352
|
const rawConfig = loadPluginConfig(args.project, args.config);
|
|
5004
7353
|
const config = parseConfig(rawConfig);
|