opencode-codebase-index 0.6.1 → 0.7.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 +279 -24
- package/commands/index.md +6 -4
- package/dist/cli.cjs +843 -122
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +843 -122
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +7935 -6816
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7899 -6780
- 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 +21 -16
- package/skill/SKILL.md +63 -10
package/dist/cli.cjs
CHANGED
|
@@ -487,7 +487,7 @@ var require_ignore = __commonJS({
|
|
|
487
487
|
// path matching.
|
|
488
488
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
489
489
|
// @returns {TestResult} true if a file is ignored
|
|
490
|
-
test(
|
|
490
|
+
test(path11, checkUnignored, mode) {
|
|
491
491
|
let ignored = false;
|
|
492
492
|
let unignored = false;
|
|
493
493
|
let matchedRule;
|
|
@@ -496,7 +496,7 @@ var require_ignore = __commonJS({
|
|
|
496
496
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
497
497
|
return;
|
|
498
498
|
}
|
|
499
|
-
const matched = rule[mode].test(
|
|
499
|
+
const matched = rule[mode].test(path11);
|
|
500
500
|
if (!matched) {
|
|
501
501
|
return;
|
|
502
502
|
}
|
|
@@ -517,17 +517,17 @@ var require_ignore = __commonJS({
|
|
|
517
517
|
var throwError = (message, Ctor) => {
|
|
518
518
|
throw new Ctor(message);
|
|
519
519
|
};
|
|
520
|
-
var checkPath = (
|
|
521
|
-
if (!isString(
|
|
520
|
+
var checkPath = (path11, originalPath, doThrow) => {
|
|
521
|
+
if (!isString(path11)) {
|
|
522
522
|
return doThrow(
|
|
523
523
|
`path must be a string, but got \`${originalPath}\``,
|
|
524
524
|
TypeError
|
|
525
525
|
);
|
|
526
526
|
}
|
|
527
|
-
if (!
|
|
527
|
+
if (!path11) {
|
|
528
528
|
return doThrow(`path must not be empty`, TypeError);
|
|
529
529
|
}
|
|
530
|
-
if (checkPath.isNotRelative(
|
|
530
|
+
if (checkPath.isNotRelative(path11)) {
|
|
531
531
|
const r = "`path.relative()`d";
|
|
532
532
|
return doThrow(
|
|
533
533
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -536,7 +536,7 @@ var require_ignore = __commonJS({
|
|
|
536
536
|
}
|
|
537
537
|
return true;
|
|
538
538
|
};
|
|
539
|
-
var isNotRelative = (
|
|
539
|
+
var isNotRelative = (path11) => REGEX_TEST_INVALID_PATH.test(path11);
|
|
540
540
|
checkPath.isNotRelative = isNotRelative;
|
|
541
541
|
checkPath.convert = (p) => p;
|
|
542
542
|
var Ignore2 = class {
|
|
@@ -566,19 +566,19 @@ var require_ignore = __commonJS({
|
|
|
566
566
|
}
|
|
567
567
|
// @returns {TestResult}
|
|
568
568
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
569
|
-
const
|
|
569
|
+
const path11 = originalPath && checkPath.convert(originalPath);
|
|
570
570
|
checkPath(
|
|
571
|
-
|
|
571
|
+
path11,
|
|
572
572
|
originalPath,
|
|
573
573
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
574
574
|
);
|
|
575
|
-
return this._t(
|
|
575
|
+
return this._t(path11, cache, checkUnignored, slices);
|
|
576
576
|
}
|
|
577
|
-
checkIgnore(
|
|
578
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
579
|
-
return this.test(
|
|
577
|
+
checkIgnore(path11) {
|
|
578
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path11)) {
|
|
579
|
+
return this.test(path11);
|
|
580
580
|
}
|
|
581
|
-
const slices =
|
|
581
|
+
const slices = path11.split(SLASH).filter(Boolean);
|
|
582
582
|
slices.pop();
|
|
583
583
|
if (slices.length) {
|
|
584
584
|
const parent = this._t(
|
|
@@ -591,18 +591,18 @@ var require_ignore = __commonJS({
|
|
|
591
591
|
return parent;
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
|
-
return this._rules.test(
|
|
594
|
+
return this._rules.test(path11, false, MODE_CHECK_IGNORE);
|
|
595
595
|
}
|
|
596
|
-
_t(
|
|
597
|
-
if (
|
|
598
|
-
return cache[
|
|
596
|
+
_t(path11, cache, checkUnignored, slices) {
|
|
597
|
+
if (path11 in cache) {
|
|
598
|
+
return cache[path11];
|
|
599
599
|
}
|
|
600
600
|
if (!slices) {
|
|
601
|
-
slices =
|
|
601
|
+
slices = path11.split(SLASH).filter(Boolean);
|
|
602
602
|
}
|
|
603
603
|
slices.pop();
|
|
604
604
|
if (!slices.length) {
|
|
605
|
-
return cache[
|
|
605
|
+
return cache[path11] = this._rules.test(path11, checkUnignored, MODE_IGNORE);
|
|
606
606
|
}
|
|
607
607
|
const parent = this._t(
|
|
608
608
|
slices.join(SLASH) + SLASH,
|
|
@@ -610,29 +610,29 @@ var require_ignore = __commonJS({
|
|
|
610
610
|
checkUnignored,
|
|
611
611
|
slices
|
|
612
612
|
);
|
|
613
|
-
return cache[
|
|
613
|
+
return cache[path11] = parent.ignored ? parent : this._rules.test(path11, checkUnignored, MODE_IGNORE);
|
|
614
614
|
}
|
|
615
|
-
ignores(
|
|
616
|
-
return this._test(
|
|
615
|
+
ignores(path11) {
|
|
616
|
+
return this._test(path11, this._ignoreCache, false).ignored;
|
|
617
617
|
}
|
|
618
618
|
createFilter() {
|
|
619
|
-
return (
|
|
619
|
+
return (path11) => !this.ignores(path11);
|
|
620
620
|
}
|
|
621
621
|
filter(paths) {
|
|
622
622
|
return makeArray(paths).filter(this.createFilter());
|
|
623
623
|
}
|
|
624
624
|
// @returns {TestResult}
|
|
625
|
-
test(
|
|
626
|
-
return this._test(
|
|
625
|
+
test(path11) {
|
|
626
|
+
return this._test(path11, this._testCache, true);
|
|
627
627
|
}
|
|
628
628
|
};
|
|
629
629
|
var factory = (options) => new Ignore2(options);
|
|
630
|
-
var isPathValid = (
|
|
630
|
+
var isPathValid = (path11) => checkPath(path11 && checkPath.convert(path11), path11, RETURN_FALSE);
|
|
631
631
|
var setupWindows = () => {
|
|
632
632
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
633
633
|
checkPath.convert = makePosix;
|
|
634
634
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
635
|
-
checkPath.isNotRelative = (
|
|
635
|
+
checkPath.isNotRelative = (path11) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path11) || isNotRelative(path11);
|
|
636
636
|
};
|
|
637
637
|
if (
|
|
638
638
|
// Detect `process` so that it can run in browsers.
|
|
@@ -649,9 +649,7 @@ var require_ignore = __commonJS({
|
|
|
649
649
|
|
|
650
650
|
// src/cli.ts
|
|
651
651
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
652
|
-
var
|
|
653
|
-
var path9 = __toESM(require("path"), 1);
|
|
654
|
-
var os4 = __toESM(require("os"), 1);
|
|
652
|
+
var path10 = __toESM(require("path"), 1);
|
|
655
653
|
|
|
656
654
|
// src/config/constants.ts
|
|
657
655
|
var DEFAULT_INCLUDE = [
|
|
@@ -664,13 +662,15 @@ var DEFAULT_INCLUDE = [
|
|
|
664
662
|
"**/*.{sql,graphql,proto}",
|
|
665
663
|
"**/*.{yaml,yml,toml}",
|
|
666
664
|
"**/*.{md,mdx}",
|
|
667
|
-
"**/*.{sh,bash,zsh}"
|
|
665
|
+
"**/*.{sh,bash,zsh}",
|
|
666
|
+
"**/*.{txt,html,htm}"
|
|
668
667
|
];
|
|
669
668
|
var DEFAULT_EXCLUDE = [
|
|
670
669
|
"**/node_modules/**",
|
|
671
670
|
"**/.git/**",
|
|
672
671
|
"**/dist/**",
|
|
673
672
|
"**/build/**",
|
|
673
|
+
"**/*build*/**",
|
|
674
674
|
"**/*.min.js",
|
|
675
675
|
"**/*.bundle.js",
|
|
676
676
|
"**/vendor/**",
|
|
@@ -679,7 +679,9 @@ var DEFAULT_EXCLUDE = [
|
|
|
679
679
|
"**/coverage/**",
|
|
680
680
|
"**/.next/**",
|
|
681
681
|
"**/.nuxt/**",
|
|
682
|
-
"**/.opencode/**"
|
|
682
|
+
"**/.opencode/**",
|
|
683
|
+
"**/.*",
|
|
684
|
+
"**/.*/**"
|
|
683
685
|
];
|
|
684
686
|
var EMBEDDING_MODELS = {
|
|
685
687
|
"google": {
|
|
@@ -788,7 +790,10 @@ function getDefaultIndexingConfig() {
|
|
|
788
790
|
autoGc: true,
|
|
789
791
|
gcIntervalDays: 7,
|
|
790
792
|
gcOrphanThreshold: 100,
|
|
791
|
-
requireProjectMarker: true
|
|
793
|
+
requireProjectMarker: true,
|
|
794
|
+
maxDepth: 5,
|
|
795
|
+
maxFilesPerDirectory: 100,
|
|
796
|
+
fallbackToTextOnMaxChunks: true
|
|
792
797
|
};
|
|
793
798
|
}
|
|
794
799
|
function getDefaultSearchConfig() {
|
|
@@ -800,12 +805,26 @@ function getDefaultSearchConfig() {
|
|
|
800
805
|
fusionStrategy: "rrf",
|
|
801
806
|
rrfK: 60,
|
|
802
807
|
rerankTopN: 20,
|
|
803
|
-
contextLines: 0
|
|
808
|
+
contextLines: 0,
|
|
809
|
+
routingHints: true
|
|
804
810
|
};
|
|
805
811
|
}
|
|
806
812
|
function isValidFusionStrategy(value) {
|
|
807
813
|
return value === "weighted" || value === "rrf";
|
|
808
814
|
}
|
|
815
|
+
function isValidRerankerProvider(value) {
|
|
816
|
+
return value === "cohere" || value === "jina" || value === "custom";
|
|
817
|
+
}
|
|
818
|
+
function getDefaultRerankerBaseUrl(provider) {
|
|
819
|
+
switch (provider) {
|
|
820
|
+
case "cohere":
|
|
821
|
+
return "https://api.cohere.ai/v1";
|
|
822
|
+
case "jina":
|
|
823
|
+
return "https://api.jina.ai/v1";
|
|
824
|
+
case "custom":
|
|
825
|
+
return "";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
809
828
|
function getDefaultDebugConfig() {
|
|
810
829
|
return {
|
|
811
830
|
enabled: false,
|
|
@@ -868,7 +887,10 @@ function parseConfig(raw) {
|
|
|
868
887
|
autoGc: typeof rawIndexing.autoGc === "boolean" ? rawIndexing.autoGc : defaultIndexing.autoGc,
|
|
869
888
|
gcIntervalDays: typeof rawIndexing.gcIntervalDays === "number" ? Math.max(1, rawIndexing.gcIntervalDays) : defaultIndexing.gcIntervalDays,
|
|
870
889
|
gcOrphanThreshold: typeof rawIndexing.gcOrphanThreshold === "number" ? Math.max(0, rawIndexing.gcOrphanThreshold) : defaultIndexing.gcOrphanThreshold,
|
|
871
|
-
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker
|
|
890
|
+
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
|
|
891
|
+
maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
|
|
892
|
+
maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
|
|
893
|
+
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
|
|
872
894
|
};
|
|
873
895
|
const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
|
|
874
896
|
const search = {
|
|
@@ -879,7 +901,8 @@ function parseConfig(raw) {
|
|
|
879
901
|
fusionStrategy: isValidFusionStrategy(rawSearch.fusionStrategy) ? rawSearch.fusionStrategy : defaultSearch.fusionStrategy,
|
|
880
902
|
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
881
903
|
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
882
|
-
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines
|
|
904
|
+
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
|
|
905
|
+
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
|
|
883
906
|
};
|
|
884
907
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
885
908
|
const debug = {
|
|
@@ -892,9 +915,14 @@ function parseConfig(raw) {
|
|
|
892
915
|
logBranch: typeof rawDebug.logBranch === "boolean" ? rawDebug.logBranch : defaultDebug.logBranch,
|
|
893
916
|
metrics: typeof rawDebug.metrics === "boolean" ? rawDebug.metrics : defaultDebug.metrics
|
|
894
917
|
};
|
|
918
|
+
const rawKnowledgeBases = input.knowledgeBases;
|
|
919
|
+
const knowledgeBases = isStringArray(rawKnowledgeBases) ? rawKnowledgeBases.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
|
|
920
|
+
const rawAdditionalInclude = input.additionalInclude;
|
|
921
|
+
const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
|
|
895
922
|
let embeddingProvider;
|
|
896
923
|
let embeddingModel = void 0;
|
|
897
924
|
let customProvider = void 0;
|
|
925
|
+
let reranker = void 0;
|
|
898
926
|
if (embeddingProviderValue === "custom") {
|
|
899
927
|
embeddingProvider = "custom";
|
|
900
928
|
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
@@ -937,6 +965,33 @@ function parseConfig(raw) {
|
|
|
937
965
|
} else {
|
|
938
966
|
embeddingProvider = "auto";
|
|
939
967
|
}
|
|
968
|
+
const rawReranker = input.reranker && typeof input.reranker === "object" ? input.reranker : {};
|
|
969
|
+
const rerankerEnabled = typeof rawReranker.enabled === "boolean" ? rawReranker.enabled : false;
|
|
970
|
+
if (rerankerEnabled) {
|
|
971
|
+
const provider = isValidRerankerProvider(rawReranker.provider) ? rawReranker.provider : "custom";
|
|
972
|
+
const model = getResolvedString(rawReranker.model, "$root.reranker.model");
|
|
973
|
+
if (!model || model.trim().length === 0) {
|
|
974
|
+
throw new Error("reranker is enabled but reranker.model is missing or invalid.");
|
|
975
|
+
}
|
|
976
|
+
const configuredBaseUrl = getResolvedString(rawReranker.baseUrl, "$root.reranker.baseUrl");
|
|
977
|
+
const baseUrl = configuredBaseUrl?.trim() || getDefaultRerankerBaseUrl(provider);
|
|
978
|
+
if (baseUrl.length === 0) {
|
|
979
|
+
throw new Error("reranker is enabled but reranker.baseUrl is missing or invalid for provider 'custom'.");
|
|
980
|
+
}
|
|
981
|
+
const apiKey = getResolvedString(rawReranker.apiKey, "$root.reranker.apiKey");
|
|
982
|
+
if ((provider === "cohere" || provider === "jina") && (!apiKey || apiKey.trim().length === 0)) {
|
|
983
|
+
throw new Error(`reranker provider '${provider}' requires reranker.apiKey when enabled.`);
|
|
984
|
+
}
|
|
985
|
+
reranker = {
|
|
986
|
+
enabled: true,
|
|
987
|
+
provider,
|
|
988
|
+
model: model.trim(),
|
|
989
|
+
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
990
|
+
apiKey: apiKey?.trim() || void 0,
|
|
991
|
+
topN: typeof rawReranker.topN === "number" ? Math.min(50, Math.max(1, Math.floor(rawReranker.topN))) : 15,
|
|
992
|
+
timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
|
|
993
|
+
};
|
|
994
|
+
}
|
|
940
995
|
return {
|
|
941
996
|
embeddingProvider,
|
|
942
997
|
embeddingModel,
|
|
@@ -944,9 +999,12 @@ function parseConfig(raw) {
|
|
|
944
999
|
scope: isValidScope(scopeValue) ? scopeValue : "project",
|
|
945
1000
|
include: includeValue ?? DEFAULT_INCLUDE,
|
|
946
1001
|
exclude: excludeValue ?? DEFAULT_EXCLUDE,
|
|
1002
|
+
additionalInclude,
|
|
947
1003
|
indexing,
|
|
948
1004
|
search,
|
|
949
|
-
debug
|
|
1005
|
+
debug,
|
|
1006
|
+
reranker,
|
|
1007
|
+
knowledgeBases
|
|
950
1008
|
};
|
|
951
1009
|
}
|
|
952
1010
|
function getDefaultModelForProvider(provider) {
|
|
@@ -980,6 +1038,8 @@ function compareSummaries(current, baseline, againstPath) {
|
|
|
980
1038
|
hitAt10: metricDelta(current.metrics.hitAt10, baseline.metrics.hitAt10),
|
|
981
1039
|
mrrAt10: metricDelta(current.metrics.mrrAt10, baseline.metrics.mrrAt10),
|
|
982
1040
|
ndcgAt10: metricDelta(current.metrics.ndcgAt10, baseline.metrics.ndcgAt10),
|
|
1041
|
+
distinctTop3Ratio: metricDelta(current.metrics.distinctTop3Ratio, baseline.metrics.distinctTop3Ratio),
|
|
1042
|
+
rawDistinctTop3Ratio: metricDelta(current.metrics.rawDistinctTop3Ratio, baseline.metrics.rawDistinctTop3Ratio),
|
|
983
1043
|
latencyP50Ms: metricDelta(current.metrics.latencyMs.p50, baseline.metrics.latencyMs.p50),
|
|
984
1044
|
latencyP95Ms: metricDelta(current.metrics.latencyMs.p95, baseline.metrics.latencyMs.p95),
|
|
985
1045
|
latencyP99Ms: metricDelta(current.metrics.latencyMs.p99, baseline.metrics.latencyMs.p99),
|
|
@@ -998,6 +1058,35 @@ function compareSummaries(current, baseline, againstPath) {
|
|
|
998
1058
|
// src/eval/reports.ts
|
|
999
1059
|
var import_fs = require("fs");
|
|
1000
1060
|
var path = __toESM(require("path"), 1);
|
|
1061
|
+
function assertFiniteNumber(value, path11) {
|
|
1062
|
+
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1063
|
+
throw new Error(`${path11} must be a finite number`);
|
|
1064
|
+
}
|
|
1065
|
+
return value;
|
|
1066
|
+
}
|
|
1067
|
+
function validateSummary(summary, summaryPath, options) {
|
|
1068
|
+
assertFiniteNumber(summary.metrics.hitAt1, `${summaryPath}.metrics.hitAt1`);
|
|
1069
|
+
assertFiniteNumber(summary.metrics.hitAt3, `${summaryPath}.metrics.hitAt3`);
|
|
1070
|
+
assertFiniteNumber(summary.metrics.hitAt5, `${summaryPath}.metrics.hitAt5`);
|
|
1071
|
+
assertFiniteNumber(summary.metrics.hitAt10, `${summaryPath}.metrics.hitAt10`);
|
|
1072
|
+
assertFiniteNumber(summary.metrics.mrrAt10, `${summaryPath}.metrics.mrrAt10`);
|
|
1073
|
+
assertFiniteNumber(summary.metrics.ndcgAt10, `${summaryPath}.metrics.ndcgAt10`);
|
|
1074
|
+
const metrics = summary.metrics;
|
|
1075
|
+
if (metrics.distinctTop3Ratio === void 0 && options?.allowLegacyDiversityMetrics) {
|
|
1076
|
+
metrics.distinctTop3Ratio = 0;
|
|
1077
|
+
}
|
|
1078
|
+
if (metrics.rawDistinctTop3Ratio === void 0 && options?.allowLegacyDiversityMetrics) {
|
|
1079
|
+
metrics.rawDistinctTop3Ratio = 0;
|
|
1080
|
+
}
|
|
1081
|
+
assertFiniteNumber(metrics.distinctTop3Ratio, `${summaryPath}.metrics.distinctTop3Ratio`);
|
|
1082
|
+
assertFiniteNumber(metrics.rawDistinctTop3Ratio, `${summaryPath}.metrics.rawDistinctTop3Ratio`);
|
|
1083
|
+
assertFiniteNumber(summary.metrics.latencyMs.p50, `${summaryPath}.metrics.latencyMs.p50`);
|
|
1084
|
+
assertFiniteNumber(summary.metrics.latencyMs.p95, `${summaryPath}.metrics.latencyMs.p95`);
|
|
1085
|
+
assertFiniteNumber(summary.metrics.latencyMs.p99, `${summaryPath}.metrics.latencyMs.p99`);
|
|
1086
|
+
assertFiniteNumber(summary.metrics.embedding.callCount, `${summaryPath}.metrics.embedding.callCount`);
|
|
1087
|
+
assertFiniteNumber(summary.metrics.embedding.estimatedCostUsd, `${summaryPath}.metrics.embedding.estimatedCostUsd`);
|
|
1088
|
+
return summary;
|
|
1089
|
+
}
|
|
1001
1090
|
function formatPct(value) {
|
|
1002
1091
|
return `${(value * 100).toFixed(2)}%`;
|
|
1003
1092
|
}
|
|
@@ -1011,9 +1100,9 @@ function signed(value, digits = 4) {
|
|
|
1011
1100
|
const formatted = value.toFixed(digits);
|
|
1012
1101
|
return value > 0 ? `+${formatted}` : formatted;
|
|
1013
1102
|
}
|
|
1014
|
-
function loadSummary(summaryPath) {
|
|
1103
|
+
function loadSummary(summaryPath, options) {
|
|
1015
1104
|
const raw = (0, import_fs.readFileSync)(summaryPath, "utf-8");
|
|
1016
|
-
return JSON.parse(raw);
|
|
1105
|
+
return validateSummary(JSON.parse(raw), summaryPath, options);
|
|
1017
1106
|
}
|
|
1018
1107
|
function createRunDirectory(outputRoot, timestampOverride) {
|
|
1019
1108
|
const timestamp = (timestampOverride ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
|
|
@@ -1048,6 +1137,8 @@ function createSummaryMarkdown(summary, comparison, gate, sweep) {
|
|
|
1048
1137
|
lines.push(`| Hit@10 | ${formatPct(summary.metrics.hitAt10)} |`);
|
|
1049
1138
|
lines.push(`| MRR@10 | ${summary.metrics.mrrAt10.toFixed(4)} |`);
|
|
1050
1139
|
lines.push(`| nDCG@10 | ${summary.metrics.ndcgAt10.toFixed(4)} |`);
|
|
1140
|
+
lines.push(`| Distinct Top@3 | ${formatPct(summary.metrics.distinctTop3Ratio)} |`);
|
|
1141
|
+
lines.push(`| Raw Distinct Top@3 | ${formatPct(summary.metrics.rawDistinctTop3Ratio)} |`);
|
|
1051
1142
|
lines.push(`| Latency p50 | ${formatMs(summary.metrics.latencyMs.p50)} |`);
|
|
1052
1143
|
lines.push(`| Latency p95 | ${formatMs(summary.metrics.latencyMs.p95)} |`);
|
|
1053
1144
|
lines.push(`| Latency p99 | ${formatMs(summary.metrics.latencyMs.p99)} |`);
|
|
@@ -1088,6 +1179,12 @@ function createSummaryMarkdown(summary, comparison, gate, sweep) {
|
|
|
1088
1179
|
lines.push(
|
|
1089
1180
|
`| nDCG@10 | ${comparison.deltas.ndcgAt10.baseline.toFixed(4)} | ${comparison.deltas.ndcgAt10.current.toFixed(4)} | ${signed(comparison.deltas.ndcgAt10.absolute)} |`
|
|
1090
1181
|
);
|
|
1182
|
+
lines.push(
|
|
1183
|
+
`| Distinct Top@3 | ${formatPct(comparison.deltas.distinctTop3Ratio.baseline)} | ${formatPct(comparison.deltas.distinctTop3Ratio.current)} | ${signed(comparison.deltas.distinctTop3Ratio.absolute)} |`
|
|
1184
|
+
);
|
|
1185
|
+
lines.push(
|
|
1186
|
+
`| Raw Distinct Top@3 | ${formatPct(comparison.deltas.rawDistinctTop3Ratio.baseline)} | ${formatPct(comparison.deltas.rawDistinctTop3Ratio.current)} | ${signed(comparison.deltas.rawDistinctTop3Ratio.absolute)} |`
|
|
1187
|
+
);
|
|
1091
1188
|
lines.push(
|
|
1092
1189
|
`| p95 latency (ms) | ${comparison.deltas.latencyP95Ms.baseline.toFixed(3)} | ${comparison.deltas.latencyP95Ms.current.toFixed(3)} | ${signed(comparison.deltas.latencyP95Ms.absolute, 3)} |`
|
|
1093
1190
|
);
|
|
@@ -1171,7 +1268,7 @@ function pTimeout(promise, options) {
|
|
|
1171
1268
|
} = options;
|
|
1172
1269
|
let timer;
|
|
1173
1270
|
let abortHandler;
|
|
1174
|
-
const wrappedPromise = new Promise((
|
|
1271
|
+
const wrappedPromise = new Promise((resolve6, reject) => {
|
|
1175
1272
|
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
1176
1273
|
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
1177
1274
|
}
|
|
@@ -1185,7 +1282,7 @@ function pTimeout(promise, options) {
|
|
|
1185
1282
|
};
|
|
1186
1283
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1187
1284
|
}
|
|
1188
|
-
promise.then(
|
|
1285
|
+
promise.then(resolve6, reject);
|
|
1189
1286
|
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
1190
1287
|
return;
|
|
1191
1288
|
}
|
|
@@ -1193,7 +1290,7 @@ function pTimeout(promise, options) {
|
|
|
1193
1290
|
timer = customTimers.setTimeout.call(void 0, () => {
|
|
1194
1291
|
if (fallback) {
|
|
1195
1292
|
try {
|
|
1196
|
-
|
|
1293
|
+
resolve6(fallback());
|
|
1197
1294
|
} catch (error) {
|
|
1198
1295
|
reject(error);
|
|
1199
1296
|
}
|
|
@@ -1203,7 +1300,7 @@ function pTimeout(promise, options) {
|
|
|
1203
1300
|
promise.cancel();
|
|
1204
1301
|
}
|
|
1205
1302
|
if (message === false) {
|
|
1206
|
-
|
|
1303
|
+
resolve6();
|
|
1207
1304
|
} else if (message instanceof Error) {
|
|
1208
1305
|
reject(message);
|
|
1209
1306
|
} else {
|
|
@@ -1267,6 +1364,17 @@ var PriorityQueue = class {
|
|
|
1267
1364
|
const [item] = this.#queue.splice(index, 1);
|
|
1268
1365
|
this.enqueue(item.run, { priority, id });
|
|
1269
1366
|
}
|
|
1367
|
+
remove(idOrRun) {
|
|
1368
|
+
const index = this.#queue.findIndex((element) => {
|
|
1369
|
+
if (typeof idOrRun === "string") {
|
|
1370
|
+
return element.id === idOrRun;
|
|
1371
|
+
}
|
|
1372
|
+
return element.run === idOrRun;
|
|
1373
|
+
});
|
|
1374
|
+
if (index !== -1) {
|
|
1375
|
+
this.#queue.splice(index, 1);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1270
1378
|
dequeue() {
|
|
1271
1379
|
const item = this.#queue.shift();
|
|
1272
1380
|
return item?.run;
|
|
@@ -1306,6 +1414,7 @@ var PQueue = class extends import_index.default {
|
|
|
1306
1414
|
#idAssigner = 1n;
|
|
1307
1415
|
// Track currently running tasks for debugging
|
|
1308
1416
|
#runningTasks = /* @__PURE__ */ new Map();
|
|
1417
|
+
#queueAbortListenerCleanupFunctions = /* @__PURE__ */ new Set();
|
|
1309
1418
|
/**
|
|
1310
1419
|
Get or set the default timeout for all tasks. Can be changed at runtime.
|
|
1311
1420
|
|
|
@@ -1593,9 +1702,11 @@ var PQueue = class extends import_index.default {
|
|
|
1593
1702
|
// Assign unique ID if not provided
|
|
1594
1703
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
1595
1704
|
};
|
|
1596
|
-
return new Promise((
|
|
1705
|
+
return new Promise((resolve6, reject) => {
|
|
1597
1706
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
1598
|
-
|
|
1707
|
+
let cleanupQueueAbortHandler = () => void 0;
|
|
1708
|
+
const run = async () => {
|
|
1709
|
+
cleanupQueueAbortHandler();
|
|
1599
1710
|
this.#pending++;
|
|
1600
1711
|
this.#runningTasks.set(taskSymbol, {
|
|
1601
1712
|
id: options.id,
|
|
@@ -1631,7 +1742,7 @@ var PQueue = class extends import_index.default {
|
|
|
1631
1742
|
})]);
|
|
1632
1743
|
}
|
|
1633
1744
|
const result = await operation;
|
|
1634
|
-
|
|
1745
|
+
resolve6(result);
|
|
1635
1746
|
this.emit("completed", result);
|
|
1636
1747
|
} catch (error) {
|
|
1637
1748
|
reject(error);
|
|
@@ -1645,7 +1756,35 @@ var PQueue = class extends import_index.default {
|
|
|
1645
1756
|
this.#next();
|
|
1646
1757
|
});
|
|
1647
1758
|
}
|
|
1648
|
-
}
|
|
1759
|
+
};
|
|
1760
|
+
this.#queue.enqueue(run, options);
|
|
1761
|
+
const removeQueuedTask = () => {
|
|
1762
|
+
if (this.#queue instanceof PriorityQueue) {
|
|
1763
|
+
this.#queue.remove(run);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
this.#queue.remove?.(options.id);
|
|
1767
|
+
};
|
|
1768
|
+
if (options.signal) {
|
|
1769
|
+
const { signal } = options;
|
|
1770
|
+
const queueAbortHandler = () => {
|
|
1771
|
+
cleanupQueueAbortHandler();
|
|
1772
|
+
removeQueuedTask();
|
|
1773
|
+
reject(signal.reason);
|
|
1774
|
+
this.#tryToStartAnother();
|
|
1775
|
+
this.emit("next");
|
|
1776
|
+
};
|
|
1777
|
+
cleanupQueueAbortHandler = () => {
|
|
1778
|
+
signal.removeEventListener("abort", queueAbortHandler);
|
|
1779
|
+
this.#queueAbortListenerCleanupFunctions.delete(cleanupQueueAbortHandler);
|
|
1780
|
+
};
|
|
1781
|
+
if (signal.aborted) {
|
|
1782
|
+
queueAbortHandler();
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
signal.addEventListener("abort", queueAbortHandler, { once: true });
|
|
1786
|
+
this.#queueAbortListenerCleanupFunctions.add(cleanupQueueAbortHandler);
|
|
1787
|
+
}
|
|
1649
1788
|
this.emit("add");
|
|
1650
1789
|
this.#tryToStartAnother();
|
|
1651
1790
|
});
|
|
@@ -1674,6 +1813,9 @@ var PQueue = class extends import_index.default {
|
|
|
1674
1813
|
Clear the queue.
|
|
1675
1814
|
*/
|
|
1676
1815
|
clear() {
|
|
1816
|
+
for (const cleanupQueueAbortHandler of this.#queueAbortListenerCleanupFunctions) {
|
|
1817
|
+
cleanupQueueAbortHandler();
|
|
1818
|
+
}
|
|
1677
1819
|
this.#queue = new this.#queueClass();
|
|
1678
1820
|
this.#clearIntervalTimer();
|
|
1679
1821
|
this.#updateRateLimitState();
|
|
@@ -1788,13 +1930,13 @@ var PQueue = class extends import_index.default {
|
|
|
1788
1930
|
});
|
|
1789
1931
|
}
|
|
1790
1932
|
async #onEvent(event, filter) {
|
|
1791
|
-
return new Promise((
|
|
1933
|
+
return new Promise((resolve6) => {
|
|
1792
1934
|
const listener = () => {
|
|
1793
1935
|
if (filter && !filter()) {
|
|
1794
1936
|
return;
|
|
1795
1937
|
}
|
|
1796
1938
|
this.off(event, listener);
|
|
1797
|
-
|
|
1939
|
+
resolve6();
|
|
1798
1940
|
};
|
|
1799
1941
|
this.on(event, listener);
|
|
1800
1942
|
});
|
|
@@ -1953,8 +2095,6 @@ var isError = (value) => objectToString.call(value) === "[object Error]";
|
|
|
1953
2095
|
var errorMessages = /* @__PURE__ */ new Set([
|
|
1954
2096
|
"network error",
|
|
1955
2097
|
// Chrome
|
|
1956
|
-
"Failed to fetch",
|
|
1957
|
-
// Chrome
|
|
1958
2098
|
"NetworkError when attempting to fetch resource.",
|
|
1959
2099
|
// Firefox
|
|
1960
2100
|
"The Internet connection appears to be offline.",
|
|
@@ -1982,6 +2122,9 @@ function isNetworkError(error) {
|
|
|
1982
2122
|
if (message.startsWith("error sending request for url")) {
|
|
1983
2123
|
return true;
|
|
1984
2124
|
}
|
|
2125
|
+
if (message === "Failed to fetch" || message.startsWith("Failed to fetch (") && message.endsWith(")")) {
|
|
2126
|
+
return true;
|
|
2127
|
+
}
|
|
1985
2128
|
return errorMessages.has(message);
|
|
1986
2129
|
}
|
|
1987
2130
|
|
|
@@ -2079,7 +2222,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2079
2222
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
2080
2223
|
options.signal?.throwIfAborted();
|
|
2081
2224
|
if (finalDelay > 0) {
|
|
2082
|
-
await new Promise((
|
|
2225
|
+
await new Promise((resolve6, reject) => {
|
|
2083
2226
|
const onAbort = () => {
|
|
2084
2227
|
clearTimeout(timeoutToken);
|
|
2085
2228
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -2087,7 +2230,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2087
2230
|
};
|
|
2088
2231
|
const timeoutToken = setTimeout(() => {
|
|
2089
2232
|
options.signal?.removeEventListener("abort", onAbort);
|
|
2090
|
-
|
|
2233
|
+
resolve6();
|
|
2091
2234
|
}, finalDelay);
|
|
2092
2235
|
if (options.unref) {
|
|
2093
2236
|
timeoutToken.unref?.();
|
|
@@ -2690,6 +2833,82 @@ var CustomEmbeddingProvider = class {
|
|
|
2690
2833
|
}
|
|
2691
2834
|
};
|
|
2692
2835
|
|
|
2836
|
+
// src/rerank/index.ts
|
|
2837
|
+
function createReranker(config) {
|
|
2838
|
+
if (!config.enabled) {
|
|
2839
|
+
return new NoOpReranker();
|
|
2840
|
+
}
|
|
2841
|
+
return new SiliconFlowReranker(config);
|
|
2842
|
+
}
|
|
2843
|
+
var NoOpReranker = class {
|
|
2844
|
+
isAvailable() {
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
async rerank(_query, documents, _topN) {
|
|
2848
|
+
return {
|
|
2849
|
+
results: documents.map((_, index) => ({ index, relevanceScore: 0 }))
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
var SiliconFlowReranker = class {
|
|
2854
|
+
config;
|
|
2855
|
+
constructor(config) {
|
|
2856
|
+
this.config = config;
|
|
2857
|
+
}
|
|
2858
|
+
isAvailable() {
|
|
2859
|
+
return this.config.enabled && !!this.config.baseUrl && !!this.config.model;
|
|
2860
|
+
}
|
|
2861
|
+
async rerank(query, documents, topN) {
|
|
2862
|
+
if (documents.length === 0) {
|
|
2863
|
+
return { results: [] };
|
|
2864
|
+
}
|
|
2865
|
+
const headers = {
|
|
2866
|
+
"Content-Type": "application/json"
|
|
2867
|
+
};
|
|
2868
|
+
if (this.config.apiKey) {
|
|
2869
|
+
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
2870
|
+
}
|
|
2871
|
+
const baseUrl = this.config.baseUrl ?? "https://api.siliconflow.cn/v1";
|
|
2872
|
+
const timeoutMs = this.config.timeoutMs ?? 3e4;
|
|
2873
|
+
const controller = new AbortController();
|
|
2874
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2875
|
+
try {
|
|
2876
|
+
const response = await fetch(`${baseUrl}/rerank`, {
|
|
2877
|
+
method: "POST",
|
|
2878
|
+
headers,
|
|
2879
|
+
body: JSON.stringify({
|
|
2880
|
+
model: this.config.model,
|
|
2881
|
+
query,
|
|
2882
|
+
documents,
|
|
2883
|
+
top_n: topN ?? this.config.topN ?? 20,
|
|
2884
|
+
return_documents: false
|
|
2885
|
+
}),
|
|
2886
|
+
signal: controller.signal
|
|
2887
|
+
});
|
|
2888
|
+
clearTimeout(timeout);
|
|
2889
|
+
if (!response.ok) {
|
|
2890
|
+
const errorText = await response.text();
|
|
2891
|
+
throw new Error(`Rerank API error: ${response.status} - ${errorText}`);
|
|
2892
|
+
}
|
|
2893
|
+
const data = await response.json();
|
|
2894
|
+
return {
|
|
2895
|
+
results: data.results.map((r) => ({
|
|
2896
|
+
index: r.index,
|
|
2897
|
+
relevanceScore: r.relevance_score,
|
|
2898
|
+
document: r.document?.text
|
|
2899
|
+
})),
|
|
2900
|
+
tokensUsed: data.meta?.tokens?.input_tokens
|
|
2901
|
+
};
|
|
2902
|
+
} catch (error) {
|
|
2903
|
+
clearTimeout(timeout);
|
|
2904
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2905
|
+
throw new Error(`Rerank API request timed out after ${timeoutMs}ms`);
|
|
2906
|
+
}
|
|
2907
|
+
throw error;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
|
|
2693
2912
|
// src/utils/files.ts
|
|
2694
2913
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2695
2914
|
var import_fs3 = require("fs");
|
|
@@ -2707,7 +2926,11 @@ function createIgnoreFilter(projectRoot) {
|
|
|
2707
2926
|
"__pycache__",
|
|
2708
2927
|
"target",
|
|
2709
2928
|
"vendor",
|
|
2710
|
-
".opencode"
|
|
2929
|
+
".opencode",
|
|
2930
|
+
".*",
|
|
2931
|
+
"**/.*",
|
|
2932
|
+
"**/.*/**",
|
|
2933
|
+
"**/*build*/**"
|
|
2711
2934
|
];
|
|
2712
2935
|
ig.add(defaultIgnores);
|
|
2713
2936
|
const gitignorePath = path3.join(projectRoot, ".gitignore");
|
|
@@ -2718,18 +2941,37 @@ function createIgnoreFilter(projectRoot) {
|
|
|
2718
2941
|
return ig;
|
|
2719
2942
|
}
|
|
2720
2943
|
function matchGlob(filePath, pattern) {
|
|
2721
|
-
|
|
2944
|
+
if (pattern.startsWith("**/")) {
|
|
2945
|
+
const withoutPrefix = pattern.slice(3);
|
|
2946
|
+
if (withoutPrefix && matchGlob(filePath, withoutPrefix)) {
|
|
2947
|
+
return true;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
const escapedPattern = pattern.replace(/[.+^$()|[\]\\]/g, "\\$&");
|
|
2951
|
+
let regexPattern = escapedPattern.replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, p1) => `(${p1.split(",").join("|")})`);
|
|
2722
2952
|
if (regexPattern.startsWith(".*/")) {
|
|
2723
2953
|
regexPattern = `(.*\\/)?${regexPattern.slice(3)}`;
|
|
2724
2954
|
}
|
|
2725
2955
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
2726
2956
|
return regex.test(filePath);
|
|
2727
2957
|
}
|
|
2728
|
-
async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped) {
|
|
2958
|
+
async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
|
|
2729
2959
|
const entries = await import_fs3.promises.readdir(dir, { withFileTypes: true });
|
|
2960
|
+
const filesInDir = [];
|
|
2961
|
+
const subdirs = [];
|
|
2730
2962
|
for (const entry of entries) {
|
|
2731
2963
|
const fullPath = path3.join(dir, entry.name);
|
|
2732
2964
|
const relativePath = path3.relative(projectRoot, fullPath);
|
|
2965
|
+
if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
|
|
2966
|
+
if (entry.isDirectory()) {
|
|
2967
|
+
skipped.push({ path: relativePath, reason: "excluded" });
|
|
2968
|
+
}
|
|
2969
|
+
continue;
|
|
2970
|
+
}
|
|
2971
|
+
if (entry.isDirectory() && entry.name.toLowerCase().includes("build")) {
|
|
2972
|
+
skipped.push({ path: relativePath, reason: "excluded" });
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2733
2975
|
if (ignoreFilter.ignores(relativePath)) {
|
|
2734
2976
|
if (entry.isFile()) {
|
|
2735
2977
|
skipped.push({ path: relativePath, reason: "gitignore" });
|
|
@@ -2737,15 +2979,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
2737
2979
|
continue;
|
|
2738
2980
|
}
|
|
2739
2981
|
if (entry.isDirectory()) {
|
|
2740
|
-
|
|
2741
|
-
fullPath,
|
|
2742
|
-
projectRoot,
|
|
2743
|
-
includePatterns,
|
|
2744
|
-
excludePatterns,
|
|
2745
|
-
ignoreFilter,
|
|
2746
|
-
maxFileSize,
|
|
2747
|
-
skipped
|
|
2748
|
-
);
|
|
2982
|
+
subdirs.push({ fullPath, relativePath });
|
|
2749
2983
|
} else if (entry.isFile()) {
|
|
2750
2984
|
const stat = await import_fs3.promises.stat(fullPath);
|
|
2751
2985
|
if (stat.size > maxFileSize) {
|
|
@@ -2766,12 +3000,37 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
2766
3000
|
}
|
|
2767
3001
|
}
|
|
2768
3002
|
if (matched) {
|
|
2769
|
-
|
|
3003
|
+
filesInDir.push({ path: fullPath, size: stat.size });
|
|
2770
3004
|
}
|
|
2771
3005
|
}
|
|
2772
3006
|
}
|
|
3007
|
+
filesInDir.sort((a, b) => a.size - b.size);
|
|
3008
|
+
const limitedFiles = filesInDir.slice(0, options.maxFilesPerDirectory);
|
|
3009
|
+
for (const f of limitedFiles) {
|
|
3010
|
+
yield f;
|
|
3011
|
+
}
|
|
3012
|
+
for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
|
|
3013
|
+
skipped.push({ path: path3.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
|
|
3014
|
+
}
|
|
3015
|
+
const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
|
|
3016
|
+
if (canRecurse) {
|
|
3017
|
+
for (const sub of subdirs) {
|
|
3018
|
+
yield* walkDirectory(
|
|
3019
|
+
sub.fullPath,
|
|
3020
|
+
projectRoot,
|
|
3021
|
+
includePatterns,
|
|
3022
|
+
excludePatterns,
|
|
3023
|
+
ignoreFilter,
|
|
3024
|
+
maxFileSize,
|
|
3025
|
+
skipped,
|
|
3026
|
+
options,
|
|
3027
|
+
currentDepth + 1
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
2773
3031
|
}
|
|
2774
|
-
async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFileSize) {
|
|
3032
|
+
async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
|
|
3033
|
+
const opts = walkOptions ?? { maxDepth: 5, maxFilesPerDirectory: 100 };
|
|
2775
3034
|
const ignoreFilter = createIgnoreFilter(projectRoot);
|
|
2776
3035
|
const files = [];
|
|
2777
3036
|
const skipped = [];
|
|
@@ -2782,10 +3041,46 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
|
|
|
2782
3041
|
excludePatterns,
|
|
2783
3042
|
ignoreFilter,
|
|
2784
3043
|
maxFileSize,
|
|
2785
|
-
skipped
|
|
3044
|
+
skipped,
|
|
3045
|
+
opts,
|
|
3046
|
+
0
|
|
2786
3047
|
)) {
|
|
2787
3048
|
files.push(file);
|
|
2788
3049
|
}
|
|
3050
|
+
if (additionalRoots && additionalRoots.length > 0) {
|
|
3051
|
+
const normalizedRoots = /* @__PURE__ */ new Set();
|
|
3052
|
+
for (const kbRoot of additionalRoots) {
|
|
3053
|
+
const resolved = path3.normalize(
|
|
3054
|
+
path3.isAbsolute(kbRoot) ? kbRoot : path3.resolve(projectRoot, kbRoot)
|
|
3055
|
+
);
|
|
3056
|
+
normalizedRoots.add(resolved);
|
|
3057
|
+
}
|
|
3058
|
+
for (const resolvedKbRoot of normalizedRoots) {
|
|
3059
|
+
try {
|
|
3060
|
+
const stat = await import_fs3.promises.stat(resolvedKbRoot);
|
|
3061
|
+
if (!stat.isDirectory()) {
|
|
3062
|
+
skipped.push({ path: resolvedKbRoot, reason: "excluded" });
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
const kbIgnoreFilter = createIgnoreFilter(resolvedKbRoot);
|
|
3066
|
+
for await (const file of walkDirectory(
|
|
3067
|
+
resolvedKbRoot,
|
|
3068
|
+
resolvedKbRoot,
|
|
3069
|
+
includePatterns,
|
|
3070
|
+
excludePatterns,
|
|
3071
|
+
kbIgnoreFilter,
|
|
3072
|
+
maxFileSize,
|
|
3073
|
+
skipped,
|
|
3074
|
+
opts,
|
|
3075
|
+
0
|
|
3076
|
+
)) {
|
|
3077
|
+
files.push(file);
|
|
3078
|
+
}
|
|
3079
|
+
} catch {
|
|
3080
|
+
skipped.push({ path: resolvedKbRoot, reason: "excluded" });
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
2789
3084
|
return { files, skipped };
|
|
2790
3085
|
}
|
|
2791
3086
|
|
|
@@ -3074,7 +3369,6 @@ var Logger = class {
|
|
|
3074
3369
|
formatMetrics() {
|
|
3075
3370
|
const m = this.metrics;
|
|
3076
3371
|
const lines = [];
|
|
3077
|
-
lines.push("=== Metrics ===");
|
|
3078
3372
|
if (m.indexingStartTime && m.indexingEndTime) {
|
|
3079
3373
|
const duration = m.indexingEndTime - m.indexingStartTime;
|
|
3080
3374
|
lines.push(`Indexing duration: ${(duration / 1e3).toFixed(2)}s`);
|
|
@@ -3187,7 +3481,52 @@ function getNativeBinding() {
|
|
|
3187
3481
|
const require2 = module2.createRequire(requireTarget);
|
|
3188
3482
|
return require2(nativePath);
|
|
3189
3483
|
}
|
|
3190
|
-
|
|
3484
|
+
function createMockNativeBinding() {
|
|
3485
|
+
const error = new Error("Native module not available. Please rebuild with 'npm run build:native'.");
|
|
3486
|
+
return {
|
|
3487
|
+
parseFile: () => {
|
|
3488
|
+
throw error;
|
|
3489
|
+
},
|
|
3490
|
+
parseFiles: () => {
|
|
3491
|
+
throw error;
|
|
3492
|
+
},
|
|
3493
|
+
hashContent: () => {
|
|
3494
|
+
throw error;
|
|
3495
|
+
},
|
|
3496
|
+
hashFile: () => {
|
|
3497
|
+
throw error;
|
|
3498
|
+
},
|
|
3499
|
+
extractCalls: () => {
|
|
3500
|
+
throw error;
|
|
3501
|
+
},
|
|
3502
|
+
VectorStore: class {
|
|
3503
|
+
constructor() {
|
|
3504
|
+
throw error;
|
|
3505
|
+
}
|
|
3506
|
+
},
|
|
3507
|
+
InvertedIndex: class {
|
|
3508
|
+
constructor() {
|
|
3509
|
+
throw error;
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
Database: class {
|
|
3513
|
+
constructor() {
|
|
3514
|
+
throw error;
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
var native;
|
|
3520
|
+
try {
|
|
3521
|
+
native = getNativeBinding();
|
|
3522
|
+
} catch (e) {
|
|
3523
|
+
console.error("[codebase-index] Failed to load native module:", e);
|
|
3524
|
+
native = createMockNativeBinding();
|
|
3525
|
+
}
|
|
3526
|
+
function parseFileAsText(filePath, content) {
|
|
3527
|
+
const result = native.parseFileAsText(filePath, content);
|
|
3528
|
+
return result.map(mapChunk);
|
|
3529
|
+
}
|
|
3191
3530
|
function parseFiles(files) {
|
|
3192
3531
|
const result = native.parseFiles(files);
|
|
3193
3532
|
return result.map((f) => ({
|
|
@@ -4013,6 +4352,32 @@ function isLikelyImplementationPath(filePath) {
|
|
|
4013
4352
|
}
|
|
4014
4353
|
return true;
|
|
4015
4354
|
}
|
|
4355
|
+
function isDocumentationPath(filePath) {
|
|
4356
|
+
const lowered = filePath.toLowerCase();
|
|
4357
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
4358
|
+
return lowered.includes("readme") || ["md", "mdx", "rst", "adoc", "txt"].includes(ext);
|
|
4359
|
+
}
|
|
4360
|
+
function classifyExternalRerankBand(candidate, preferSourcePaths, docIntent) {
|
|
4361
|
+
const isDocOrTest = isTestOrDocPath(candidate.metadata.filePath);
|
|
4362
|
+
const isDocumentation = isDocumentationPath(candidate.metadata.filePath);
|
|
4363
|
+
const isImplementation = isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType);
|
|
4364
|
+
if (preferSourcePaths) {
|
|
4365
|
+
if (isImplementation) return "implementation";
|
|
4366
|
+
if (isDocumentation) return "documentation";
|
|
4367
|
+
if (isDocOrTest) return "test";
|
|
4368
|
+
return "other";
|
|
4369
|
+
}
|
|
4370
|
+
if (docIntent) {
|
|
4371
|
+
if (isDocumentation) return "documentation";
|
|
4372
|
+
if (isImplementation) return "implementation";
|
|
4373
|
+
if (isDocOrTest) return "test";
|
|
4374
|
+
return "other";
|
|
4375
|
+
}
|
|
4376
|
+
if (isImplementation) return "implementation";
|
|
4377
|
+
if (isDocumentation) return "documentation";
|
|
4378
|
+
if (isDocOrTest) return "test";
|
|
4379
|
+
return "other";
|
|
4380
|
+
}
|
|
4016
4381
|
function classifyQueryIntent(tokens) {
|
|
4017
4382
|
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
4018
4383
|
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
@@ -4387,8 +4752,74 @@ function rerankResults(query, candidates, rerankTopN, options) {
|
|
|
4387
4752
|
return 0;
|
|
4388
4753
|
});
|
|
4389
4754
|
}
|
|
4755
|
+
const shouldDiversify = !(preferSourcePaths && identifierHints.length > 0);
|
|
4756
|
+
const diversifiedHead = diversifyEntriesByFileAndSymbol(head, (entry) => entry.candidate, shouldDiversify);
|
|
4390
4757
|
const tail = candidates.slice(topN);
|
|
4391
|
-
return [...
|
|
4758
|
+
return [...diversifiedHead.map((entry) => entry.candidate), ...tail];
|
|
4759
|
+
}
|
|
4760
|
+
function diversifyEntriesByFileAndSymbol(entries, getCandidate, enabled) {
|
|
4761
|
+
if (!enabled || entries.length <= 2) {
|
|
4762
|
+
return entries;
|
|
4763
|
+
}
|
|
4764
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4765
|
+
const groupOrder = [];
|
|
4766
|
+
for (const entry of entries) {
|
|
4767
|
+
const candidate = getCandidate(entry);
|
|
4768
|
+
const filePath = candidate.metadata.filePath;
|
|
4769
|
+
if (!groups.has(filePath)) {
|
|
4770
|
+
groups.set(filePath, []);
|
|
4771
|
+
groupOrder.push(filePath);
|
|
4772
|
+
}
|
|
4773
|
+
groups.get(filePath)?.push(entry);
|
|
4774
|
+
}
|
|
4775
|
+
const diversifiedGroups = groupOrder.map((filePath) => {
|
|
4776
|
+
const group = groups.get(filePath) ?? [];
|
|
4777
|
+
return diversifyGroupBySymbol(group, getCandidate);
|
|
4778
|
+
});
|
|
4779
|
+
const result = [];
|
|
4780
|
+
let added = true;
|
|
4781
|
+
let round = 0;
|
|
4782
|
+
while (added) {
|
|
4783
|
+
added = false;
|
|
4784
|
+
for (const group of diversifiedGroups) {
|
|
4785
|
+
const entry = group[round];
|
|
4786
|
+
if (entry !== void 0) {
|
|
4787
|
+
result.push(entry);
|
|
4788
|
+
added = true;
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
round += 1;
|
|
4792
|
+
}
|
|
4793
|
+
return result;
|
|
4794
|
+
}
|
|
4795
|
+
function diversifyCandidatesByFile(candidates, enabled) {
|
|
4796
|
+
return diversifyEntriesByFileAndSymbol(candidates, (candidate) => candidate, enabled);
|
|
4797
|
+
}
|
|
4798
|
+
function diversifyGroupBySymbol(entries, getCandidate) {
|
|
4799
|
+
if (entries.length <= 2) {
|
|
4800
|
+
return entries;
|
|
4801
|
+
}
|
|
4802
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
4803
|
+
const primary = [];
|
|
4804
|
+
const remainder = [];
|
|
4805
|
+
for (const entry of entries) {
|
|
4806
|
+
const key = buildDiversityKey(getCandidate(entry).metadata);
|
|
4807
|
+
if (!seenKeys.has(key)) {
|
|
4808
|
+
seenKeys.add(key);
|
|
4809
|
+
primary.push(entry);
|
|
4810
|
+
} else {
|
|
4811
|
+
remainder.push(entry);
|
|
4812
|
+
}
|
|
4813
|
+
}
|
|
4814
|
+
return [...primary, ...remainder];
|
|
4815
|
+
}
|
|
4816
|
+
function buildDiversityKey(metadata) {
|
|
4817
|
+
const normalizedPath = metadata.filePath.toLowerCase();
|
|
4818
|
+
const normalizedName = (metadata.name ?? "").trim().toLowerCase();
|
|
4819
|
+
if (normalizedName.length > 0) {
|
|
4820
|
+
return `${normalizedPath}#${normalizedName}`;
|
|
4821
|
+
}
|
|
4822
|
+
return normalizedPath;
|
|
4392
4823
|
}
|
|
4393
4824
|
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4394
4825
|
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
@@ -4716,6 +5147,7 @@ var Indexer = class {
|
|
|
4716
5147
|
database = null;
|
|
4717
5148
|
provider = null;
|
|
4718
5149
|
configuredProviderInfo = null;
|
|
5150
|
+
reranker = null;
|
|
4719
5151
|
fileHashCache = /* @__PURE__ */ new Map();
|
|
4720
5152
|
fileHashCachePath = "";
|
|
4721
5153
|
failedBatchesPath = "";
|
|
@@ -4845,6 +5277,152 @@ var Indexer = class {
|
|
|
4845
5277
|
return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
4846
5278
|
}
|
|
4847
5279
|
}
|
|
5280
|
+
async rerankCandidatesWithApi(query, candidates, options) {
|
|
5281
|
+
const reranker = this.config.reranker;
|
|
5282
|
+
if (!reranker || !reranker.enabled || candidates.length <= 1) {
|
|
5283
|
+
return candidates;
|
|
5284
|
+
}
|
|
5285
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
5286
|
+
const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
|
|
5287
|
+
const docIntent = classifyDocIntent(queryTokens) === "docs";
|
|
5288
|
+
if (options?.definitionIntent === true) {
|
|
5289
|
+
return candidates;
|
|
5290
|
+
}
|
|
5291
|
+
if (options?.hasIdentifierHints === true && preferSourcePaths && !docIntent) {
|
|
5292
|
+
return candidates;
|
|
5293
|
+
}
|
|
5294
|
+
const topN = Math.min(reranker.topN, candidates.length);
|
|
5295
|
+
const head = candidates.slice(0, topN);
|
|
5296
|
+
const tail = candidates.slice(topN);
|
|
5297
|
+
const grouped = /* @__PURE__ */ new Map([
|
|
5298
|
+
["implementation", []],
|
|
5299
|
+
["documentation", []],
|
|
5300
|
+
["test", []],
|
|
5301
|
+
["other", []]
|
|
5302
|
+
]);
|
|
5303
|
+
for (const candidate of head) {
|
|
5304
|
+
const band = classifyExternalRerankBand(candidate, preferSourcePaths, docIntent);
|
|
5305
|
+
grouped.get(band)?.push(candidate);
|
|
5306
|
+
}
|
|
5307
|
+
const orderedBands = preferSourcePaths ? ["implementation", "other", "documentation", "test"] : docIntent ? ["documentation", "implementation", "other", "test"] : ["implementation", "other", "documentation", "test"];
|
|
5308
|
+
try {
|
|
5309
|
+
const rerankedHead = [];
|
|
5310
|
+
for (const band of orderedBands) {
|
|
5311
|
+
const bandCandidates = grouped.get(band) ?? [];
|
|
5312
|
+
if (bandCandidates.length <= 1) {
|
|
5313
|
+
rerankedHead.push(...bandCandidates);
|
|
5314
|
+
continue;
|
|
5315
|
+
}
|
|
5316
|
+
const documents = await Promise.all(
|
|
5317
|
+
bandCandidates.map(async (candidate) => ({
|
|
5318
|
+
id: candidate.id,
|
|
5319
|
+
text: await this.createRerankerDocumentText(candidate)
|
|
5320
|
+
}))
|
|
5321
|
+
);
|
|
5322
|
+
const rankedIds = await this.callExternalReranker(query, documents, reranker);
|
|
5323
|
+
if (rankedIds.length === 0) {
|
|
5324
|
+
rerankedHead.push(...bandCandidates);
|
|
5325
|
+
continue;
|
|
5326
|
+
}
|
|
5327
|
+
const order = new Map(rankedIds.map((id, index) => [id, index]));
|
|
5328
|
+
const bandReranked = [...bandCandidates].sort((a, b) => {
|
|
5329
|
+
const aRank = order.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
5330
|
+
const bRank = order.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
5331
|
+
if (aRank !== bRank) {
|
|
5332
|
+
return aRank - bRank;
|
|
5333
|
+
}
|
|
5334
|
+
if (b.score !== a.score) {
|
|
5335
|
+
return b.score - a.score;
|
|
5336
|
+
}
|
|
5337
|
+
return a.id.localeCompare(b.id);
|
|
5338
|
+
});
|
|
5339
|
+
const shouldDiversifyBand = !options?.hasIdentifierHints;
|
|
5340
|
+
rerankedHead.push(...diversifyCandidatesByFile(bandReranked, shouldDiversifyBand));
|
|
5341
|
+
}
|
|
5342
|
+
this.logger.search("debug", "Applied external reranker", {
|
|
5343
|
+
provider: reranker.provider,
|
|
5344
|
+
model: reranker.model,
|
|
5345
|
+
candidateCount: head.length,
|
|
5346
|
+
bands: orderedBands
|
|
5347
|
+
});
|
|
5348
|
+
return [...rerankedHead, ...tail];
|
|
5349
|
+
} catch (error) {
|
|
5350
|
+
this.logger.search("warn", "External reranker failed; using deterministic order", {
|
|
5351
|
+
provider: reranker.provider,
|
|
5352
|
+
model: reranker.model,
|
|
5353
|
+
error: getErrorMessage(error)
|
|
5354
|
+
});
|
|
5355
|
+
return candidates;
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
async callExternalReranker(query, documents, reranker) {
|
|
5359
|
+
const headers = {
|
|
5360
|
+
"Content-Type": "application/json"
|
|
5361
|
+
};
|
|
5362
|
+
if (reranker.apiKey) {
|
|
5363
|
+
headers.Authorization = `Bearer ${reranker.apiKey}`;
|
|
5364
|
+
}
|
|
5365
|
+
const controller = new AbortController();
|
|
5366
|
+
const timeout = setTimeout(() => controller.abort(), reranker.timeoutMs);
|
|
5367
|
+
try {
|
|
5368
|
+
const response = await fetch(`${reranker.baseUrl}/rerank`, {
|
|
5369
|
+
method: "POST",
|
|
5370
|
+
headers,
|
|
5371
|
+
body: JSON.stringify({
|
|
5372
|
+
model: reranker.model,
|
|
5373
|
+
query,
|
|
5374
|
+
documents: documents.map((document) => document.text),
|
|
5375
|
+
top_n: documents.length,
|
|
5376
|
+
return_documents: false
|
|
5377
|
+
}),
|
|
5378
|
+
signal: controller.signal
|
|
5379
|
+
});
|
|
5380
|
+
if (!response.ok) {
|
|
5381
|
+
throw new Error(`Reranker API error: ${response.status} - ${await response.text()}`);
|
|
5382
|
+
}
|
|
5383
|
+
const body = await response.json();
|
|
5384
|
+
if (!Array.isArray(body.results)) {
|
|
5385
|
+
throw new Error("Reranker API returned unexpected response format.");
|
|
5386
|
+
}
|
|
5387
|
+
return body.results.map((result) => {
|
|
5388
|
+
const index = typeof result.index === "number" ? result.index : -1;
|
|
5389
|
+
return documents[index]?.id;
|
|
5390
|
+
}).filter((id) => typeof id === "string");
|
|
5391
|
+
} catch (error) {
|
|
5392
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
5393
|
+
throw new Error(`Reranker request timed out after ${reranker.timeoutMs}ms`);
|
|
5394
|
+
}
|
|
5395
|
+
throw error;
|
|
5396
|
+
} finally {
|
|
5397
|
+
clearTimeout(timeout);
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
async createRerankerDocumentText(candidate) {
|
|
5401
|
+
const parts = [
|
|
5402
|
+
`path: ${candidate.metadata.filePath}`,
|
|
5403
|
+
`chunk_type: ${candidate.metadata.chunkType}`,
|
|
5404
|
+
`language: ${candidate.metadata.language}`,
|
|
5405
|
+
`lines: ${candidate.metadata.startLine}-${candidate.metadata.endLine}`
|
|
5406
|
+
];
|
|
5407
|
+
if (candidate.metadata.name) {
|
|
5408
|
+
parts.push(`name: ${candidate.metadata.name}`);
|
|
5409
|
+
}
|
|
5410
|
+
const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
|
|
5411
|
+
parts.push(`intent_hint: ${intent}`);
|
|
5412
|
+
try {
|
|
5413
|
+
const fileContent = await import_fs5.promises.readFile(candidate.metadata.filePath, "utf-8");
|
|
5414
|
+
const lines = fileContent.split("\n");
|
|
5415
|
+
const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
|
|
5416
|
+
const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
|
|
5417
|
+
const snippet = lines.slice(snippetStartLine - 1, snippetEndLine).join("\n").trim();
|
|
5418
|
+
parts.push("snippet:");
|
|
5419
|
+
parts.push(snippet.length > 0 ? snippet : "[empty]");
|
|
5420
|
+
} catch {
|
|
5421
|
+
parts.push("snippet:");
|
|
5422
|
+
parts.push("[unavailable]");
|
|
5423
|
+
}
|
|
5424
|
+
return parts.join("\n");
|
|
5425
|
+
}
|
|
4848
5426
|
async initialize() {
|
|
4849
5427
|
if (this.config.embeddingProvider === "custom") {
|
|
4850
5428
|
if (!this.config.customProvider) {
|
|
@@ -4864,9 +5442,19 @@ var Indexer = class {
|
|
|
4864
5442
|
this.logger.info("Initializing indexer", {
|
|
4865
5443
|
provider: this.configuredProviderInfo.provider,
|
|
4866
5444
|
model: this.configuredProviderInfo.modelInfo.model,
|
|
4867
|
-
scope: this.config.scope
|
|
5445
|
+
scope: this.config.scope,
|
|
5446
|
+
rerankerEnabled: this.config.reranker?.enabled ?? false
|
|
4868
5447
|
});
|
|
4869
5448
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
5449
|
+
if (this.config.reranker?.enabled) {
|
|
5450
|
+
this.reranker = createReranker(this.config.reranker);
|
|
5451
|
+
if (this.reranker.isAvailable()) {
|
|
5452
|
+
this.logger.info("Reranker initialized", {
|
|
5453
|
+
model: this.config.reranker.model,
|
|
5454
|
+
baseUrl: this.config.reranker.baseUrl
|
|
5455
|
+
});
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
4870
5458
|
await import_fs5.promises.mkdir(this.indexPath, { recursive: true });
|
|
4871
5459
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
4872
5460
|
const storePath = path6.join(this.indexPath, "vectors");
|
|
@@ -5056,11 +5644,14 @@ var Indexer = class {
|
|
|
5056
5644
|
}
|
|
5057
5645
|
async estimateCost() {
|
|
5058
5646
|
const { configuredProviderInfo } = await this.ensureInitialized();
|
|
5647
|
+
const includePatterns = [...this.config.include, ...this.config.additionalInclude];
|
|
5059
5648
|
const { files } = await collectFiles(
|
|
5060
5649
|
this.projectRoot,
|
|
5061
|
-
|
|
5650
|
+
includePatterns,
|
|
5062
5651
|
this.config.exclude,
|
|
5063
|
-
this.config.indexing.maxFileSize
|
|
5652
|
+
this.config.indexing.maxFileSize,
|
|
5653
|
+
this.config.knowledgeBases,
|
|
5654
|
+
{ maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
|
|
5064
5655
|
);
|
|
5065
5656
|
return createCostEstimate(files, configuredProviderInfo);
|
|
5066
5657
|
}
|
|
@@ -5095,11 +5686,14 @@ var Indexer = class {
|
|
|
5095
5686
|
totalChunks: 0
|
|
5096
5687
|
});
|
|
5097
5688
|
this.loadFileHashCache();
|
|
5689
|
+
const includePatterns = [...this.config.include, ...this.config.additionalInclude];
|
|
5098
5690
|
const { files, skipped } = await collectFiles(
|
|
5099
5691
|
this.projectRoot,
|
|
5100
|
-
|
|
5692
|
+
includePatterns,
|
|
5101
5693
|
this.config.exclude,
|
|
5102
|
-
this.config.indexing.maxFileSize
|
|
5694
|
+
this.config.indexing.maxFileSize,
|
|
5695
|
+
this.config.knowledgeBases,
|
|
5696
|
+
{ maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
|
|
5103
5697
|
);
|
|
5104
5698
|
stats.totalFiles = files.length;
|
|
5105
5699
|
stats.skippedFiles = skipped;
|
|
@@ -5168,7 +5762,15 @@ var Indexer = class {
|
|
|
5168
5762
|
stats.parseFailures.push(relativePath);
|
|
5169
5763
|
}
|
|
5170
5764
|
let fileChunkCount = 0;
|
|
5171
|
-
|
|
5765
|
+
let chunksToProcess = parsed.chunks;
|
|
5766
|
+
if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
|
|
5767
|
+
const changedFile = changedFiles.find((f) => f.path === parsed.path);
|
|
5768
|
+
if (changedFile) {
|
|
5769
|
+
const textChunks = parseFileAsText(parsed.path, changedFile.content);
|
|
5770
|
+
chunksToProcess = textChunks;
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
for (const chunk of chunksToProcess) {
|
|
5172
5774
|
if (fileChunkCount >= this.config.indexing.maxChunksPerFile) {
|
|
5173
5775
|
break;
|
|
5174
5776
|
}
|
|
@@ -5375,7 +5977,7 @@ var Indexer = class {
|
|
|
5375
5977
|
for (const batch of dynamicBatches) {
|
|
5376
5978
|
queue.add(async () => {
|
|
5377
5979
|
if (rateLimitBackoffMs > 0) {
|
|
5378
|
-
await new Promise((
|
|
5980
|
+
await new Promise((resolve6) => setTimeout(resolve6, rateLimitBackoffMs));
|
|
5379
5981
|
}
|
|
5380
5982
|
try {
|
|
5381
5983
|
const result = await pRetry(
|
|
@@ -5580,6 +6182,7 @@ var Indexer = class {
|
|
|
5580
6182
|
const rerankTopN = this.config.search.rerankTopN;
|
|
5581
6183
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5582
6184
|
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
6185
|
+
const identifierHints = extractIdentifierHints(query);
|
|
5583
6186
|
this.logger.search("debug", "Starting search", {
|
|
5584
6187
|
query,
|
|
5585
6188
|
maxResults,
|
|
@@ -5634,10 +6237,14 @@ var Indexer = class {
|
|
|
5634
6237
|
hybridWeight,
|
|
5635
6238
|
prioritizeSourcePaths: sourceIntent
|
|
5636
6239
|
});
|
|
6240
|
+
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
6241
|
+
definitionIntent: options?.definitionIntent === true,
|
|
6242
|
+
hasIdentifierHints: identifierHints.length > 0
|
|
6243
|
+
});
|
|
5637
6244
|
const fusionMs = import_perf_hooks.performance.now() - fusionStartTime;
|
|
5638
6245
|
const rescued = promoteIdentifierMatches(
|
|
5639
6246
|
query,
|
|
5640
|
-
|
|
6247
|
+
rerankedCombined,
|
|
5641
6248
|
semanticCandidates,
|
|
5642
6249
|
keywordCandidates,
|
|
5643
6250
|
database,
|
|
@@ -5668,7 +6275,7 @@ var Indexer = class {
|
|
|
5668
6275
|
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5669
6276
|
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5670
6277
|
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5671
|
-
const hasCodeHints = extractCodeTermHints(query).length > 0 ||
|
|
6278
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
|
|
5672
6279
|
const baseFiltered = tiered.filter((r) => {
|
|
5673
6280
|
if (r.score < this.config.search.minScore) return false;
|
|
5674
6281
|
if (options?.fileType) {
|
|
@@ -5688,6 +6295,7 @@ var Indexer = class {
|
|
|
5688
6295
|
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5689
6296
|
);
|
|
5690
6297
|
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
6298
|
+
const finalResults = filtered;
|
|
5691
6299
|
const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
|
|
5692
6300
|
this.logger.recordSearch(totalSearchMs, {
|
|
5693
6301
|
embeddingMs,
|
|
@@ -5697,7 +6305,7 @@ var Indexer = class {
|
|
|
5697
6305
|
});
|
|
5698
6306
|
this.logger.search("info", "Search complete", {
|
|
5699
6307
|
query,
|
|
5700
|
-
results:
|
|
6308
|
+
results: finalResults.length,
|
|
5701
6309
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
5702
6310
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
5703
6311
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
@@ -5707,7 +6315,7 @@ var Indexer = class {
|
|
|
5707
6315
|
});
|
|
5708
6316
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
5709
6317
|
return Promise.all(
|
|
5710
|
-
|
|
6318
|
+
finalResults.map(async (r) => {
|
|
5711
6319
|
let content = "";
|
|
5712
6320
|
let contextStartLine = r.metadata.startLine;
|
|
5713
6321
|
let contextEndLine = r.metadata.endLine;
|
|
@@ -6041,6 +6649,12 @@ function evaluateBudgetGate(budget, summary, comparison) {
|
|
|
6041
6649
|
message: `MRR@10 ${summary.metrics.mrrAt10.toFixed(4)} is below minimum ${thresholds.minMrrAt10.toFixed(4)}`
|
|
6042
6650
|
});
|
|
6043
6651
|
}
|
|
6652
|
+
if (thresholds.minRawDistinctTop3Ratio !== void 0 && summary.metrics.rawDistinctTop3Ratio < thresholds.minRawDistinctTop3Ratio) {
|
|
6653
|
+
violations.push({
|
|
6654
|
+
metric: "minRawDistinctTop3Ratio",
|
|
6655
|
+
message: `Raw Distinct Top@3 ${summary.metrics.rawDistinctTop3Ratio.toFixed(4)} is below minimum ${thresholds.minRawDistinctTop3Ratio.toFixed(4)}`
|
|
6656
|
+
});
|
|
6657
|
+
}
|
|
6044
6658
|
if (comparison) {
|
|
6045
6659
|
if (thresholds.hitAt5MaxDrop !== void 0 && comparison.deltas.hitAt5.absolute < -thresholds.hitAt5MaxDrop) {
|
|
6046
6660
|
violations.push({
|
|
@@ -6054,6 +6668,12 @@ function evaluateBudgetGate(budget, summary, comparison) {
|
|
|
6054
6668
|
message: `MRR@10 drop ${comparison.deltas.mrrAt10.absolute.toFixed(4)} exceeds allowed -${thresholds.mrrAt10MaxDrop.toFixed(4)}`
|
|
6055
6669
|
});
|
|
6056
6670
|
}
|
|
6671
|
+
if (thresholds.rawDistinctTop3RatioMaxDrop !== void 0 && comparison.deltas.rawDistinctTop3Ratio.absolute < -thresholds.rawDistinctTop3RatioMaxDrop) {
|
|
6672
|
+
violations.push({
|
|
6673
|
+
metric: "rawDistinctTop3RatioMaxDrop",
|
|
6674
|
+
message: `Raw Distinct Top@3 drop ${comparison.deltas.rawDistinctTop3Ratio.absolute.toFixed(4)} exceeds allowed -${thresholds.rawDistinctTop3RatioMaxDrop.toFixed(4)}`
|
|
6675
|
+
});
|
|
6676
|
+
}
|
|
6057
6677
|
if (thresholds.p95LatencyMaxMultiplier !== void 0) {
|
|
6058
6678
|
const baselineP95 = comparison.deltas.latencyP95Ms.baseline;
|
|
6059
6679
|
if (baselineP95 > BASELINE_P95_EPSILON_MS) {
|
|
@@ -6108,6 +6728,12 @@ function uniqueResultsByPath(results) {
|
|
|
6108
6728
|
}
|
|
6109
6729
|
return unique;
|
|
6110
6730
|
}
|
|
6731
|
+
function distinctTopKRatio(results, k) {
|
|
6732
|
+
const top = results.slice(0, k);
|
|
6733
|
+
if (top.length === 0) return 0;
|
|
6734
|
+
const distinct = new Set(top.map((result) => normalizePath(result.filePath))).size;
|
|
6735
|
+
return distinct / top.length;
|
|
6736
|
+
}
|
|
6111
6737
|
function pathMatchesExpected(actualPath, expectedPath) {
|
|
6112
6738
|
const actual = normalizePath(actualPath);
|
|
6113
6739
|
const expected = normalizePath(expectedPath);
|
|
@@ -6186,6 +6812,7 @@ function buildPerQueryResult(query, results, latencyMs, k) {
|
|
|
6186
6812
|
reciprocalRankAt10: reciprocalRankAtK(deduped, relevantPaths, 10),
|
|
6187
6813
|
ndcgAt10: ndcgAtK(deduped, relevantPaths, 10),
|
|
6188
6814
|
failureBucket: classifyFailureBucket(query, results, k),
|
|
6815
|
+
rawTop3DistinctRatio: distinctTopKRatio(results, 3),
|
|
6189
6816
|
results: deduped
|
|
6190
6817
|
};
|
|
6191
6818
|
return perQuery;
|
|
@@ -6199,7 +6826,9 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6199
6826
|
hitAt5: 0,
|
|
6200
6827
|
hitAt10: 0,
|
|
6201
6828
|
mrrAt10: 0,
|
|
6202
|
-
ndcgAt10: 0
|
|
6829
|
+
ndcgAt10: 0,
|
|
6830
|
+
distinctTop3Ratio: 0,
|
|
6831
|
+
rawDistinctTop3Ratio: 0
|
|
6203
6832
|
};
|
|
6204
6833
|
const failureBuckets = {
|
|
6205
6834
|
"wrong-file": 0,
|
|
@@ -6215,6 +6844,8 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6215
6844
|
if (query.hitAt10) sum.hitAt10 += 1;
|
|
6216
6845
|
sum.mrrAt10 += query.reciprocalRankAt10;
|
|
6217
6846
|
sum.ndcgAt10 += query.ndcgAt10;
|
|
6847
|
+
sum.distinctTop3Ratio += distinctTopKRatio(query.results, 3);
|
|
6848
|
+
sum.rawDistinctTop3Ratio += query.rawTop3DistinctRatio;
|
|
6218
6849
|
if (query.failureBucket) {
|
|
6219
6850
|
failureBuckets[query.failureBucket] += 1;
|
|
6220
6851
|
}
|
|
@@ -6227,6 +6858,8 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6227
6858
|
hitAt10: safeDiv(sum.hitAt10),
|
|
6228
6859
|
mrrAt10: safeDiv(sum.mrrAt10),
|
|
6229
6860
|
ndcgAt10: safeDiv(sum.ndcgAt10),
|
|
6861
|
+
distinctTop3Ratio: safeDiv(sum.distinctTop3Ratio),
|
|
6862
|
+
rawDistinctTop3Ratio: safeDiv(sum.rawDistinctTop3Ratio),
|
|
6230
6863
|
latencyMs: {
|
|
6231
6864
|
p50: percentile(latencies, 0.5),
|
|
6232
6865
|
p95: percentile(latencies, 0.95),
|
|
@@ -6257,23 +6890,23 @@ function isRecord(value) {
|
|
|
6257
6890
|
function isStringArray2(value) {
|
|
6258
6891
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6259
6892
|
}
|
|
6260
|
-
function asPositiveNumber(value,
|
|
6893
|
+
function asPositiveNumber(value, path11) {
|
|
6261
6894
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
6262
|
-
throw new Error(`${
|
|
6895
|
+
throw new Error(`${path11} must be a non-negative number`);
|
|
6263
6896
|
}
|
|
6264
6897
|
return value;
|
|
6265
6898
|
}
|
|
6266
|
-
function parseQueryType(value,
|
|
6899
|
+
function parseQueryType(value, path11) {
|
|
6267
6900
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
6268
6901
|
return value;
|
|
6269
6902
|
}
|
|
6270
6903
|
throw new Error(
|
|
6271
|
-
`${
|
|
6904
|
+
`${path11} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
6272
6905
|
);
|
|
6273
6906
|
}
|
|
6274
|
-
function parseExpected(input,
|
|
6907
|
+
function parseExpected(input, path11) {
|
|
6275
6908
|
if (!isRecord(input)) {
|
|
6276
|
-
throw new Error(`${
|
|
6909
|
+
throw new Error(`${path11} must be an object`);
|
|
6277
6910
|
}
|
|
6278
6911
|
const filePathRaw = input.filePath;
|
|
6279
6912
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -6282,16 +6915,16 @@ function parseExpected(input, path10) {
|
|
|
6282
6915
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
6283
6916
|
const acceptableFiles = isStringArray2(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
6284
6917
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
6285
|
-
throw new Error(`${
|
|
6918
|
+
throw new Error(`${path11} must include either expected.filePath or expected.acceptableFiles`);
|
|
6286
6919
|
}
|
|
6287
6920
|
if (acceptableFilesRaw !== void 0 && !isStringArray2(acceptableFilesRaw)) {
|
|
6288
|
-
throw new Error(`${
|
|
6921
|
+
throw new Error(`${path11}.acceptableFiles must be an array of strings`);
|
|
6289
6922
|
}
|
|
6290
6923
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
6291
|
-
throw new Error(`${
|
|
6924
|
+
throw new Error(`${path11}.symbol must be a string when provided`);
|
|
6292
6925
|
}
|
|
6293
6926
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
6294
|
-
throw new Error(`${
|
|
6927
|
+
throw new Error(`${path11}.branch must be a string when provided`);
|
|
6295
6928
|
}
|
|
6296
6929
|
return {
|
|
6297
6930
|
filePath,
|
|
@@ -6301,25 +6934,25 @@ function parseExpected(input, path10) {
|
|
|
6301
6934
|
};
|
|
6302
6935
|
}
|
|
6303
6936
|
function parseQuery(input, index) {
|
|
6304
|
-
const
|
|
6937
|
+
const path11 = `queries[${index}]`;
|
|
6305
6938
|
if (!isRecord(input)) {
|
|
6306
|
-
throw new Error(`${
|
|
6939
|
+
throw new Error(`${path11} must be an object`);
|
|
6307
6940
|
}
|
|
6308
6941
|
const id = input.id;
|
|
6309
6942
|
const query = input.query;
|
|
6310
6943
|
const queryType = input.queryType;
|
|
6311
6944
|
const expected = input.expected;
|
|
6312
6945
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
6313
|
-
throw new Error(`${
|
|
6946
|
+
throw new Error(`${path11}.id must be a non-empty string`);
|
|
6314
6947
|
}
|
|
6315
6948
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
6316
|
-
throw new Error(`${
|
|
6949
|
+
throw new Error(`${path11}.query must be a non-empty string`);
|
|
6317
6950
|
}
|
|
6318
6951
|
return {
|
|
6319
6952
|
id,
|
|
6320
6953
|
query,
|
|
6321
|
-
queryType: parseQueryType(queryType, `${
|
|
6322
|
-
expected: parseExpected(expected, `${
|
|
6954
|
+
queryType: parseQueryType(queryType, `${path11}.queryType`),
|
|
6955
|
+
expected: parseExpected(expected, `${path11}.expected`)
|
|
6323
6956
|
};
|
|
6324
6957
|
}
|
|
6325
6958
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -6388,6 +7021,10 @@ function parseBudget(raw, sourceLabel) {
|
|
|
6388
7021
|
thresholds: {
|
|
6389
7022
|
hitAt5MaxDrop: thresholds.hitAt5MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.hitAt5MaxDrop, `${sourceLabel}.thresholds.hitAt5MaxDrop`),
|
|
6390
7023
|
mrrAt10MaxDrop: thresholds.mrrAt10MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.mrrAt10MaxDrop, `${sourceLabel}.thresholds.mrrAt10MaxDrop`),
|
|
7024
|
+
rawDistinctTop3RatioMaxDrop: thresholds.rawDistinctTop3RatioMaxDrop === void 0 ? void 0 : asPositiveNumber(
|
|
7025
|
+
thresholds.rawDistinctTop3RatioMaxDrop,
|
|
7026
|
+
`${sourceLabel}.thresholds.rawDistinctTop3RatioMaxDrop`
|
|
7027
|
+
),
|
|
6391
7028
|
p95LatencyMaxMultiplier: thresholds.p95LatencyMaxMultiplier === void 0 ? void 0 : asPositiveNumber(
|
|
6392
7029
|
thresholds.p95LatencyMaxMultiplier,
|
|
6393
7030
|
`${sourceLabel}.thresholds.p95LatencyMaxMultiplier`
|
|
@@ -6397,7 +7034,11 @@ function parseBudget(raw, sourceLabel) {
|
|
|
6397
7034
|
`${sourceLabel}.thresholds.p95LatencyMaxAbsoluteMs`
|
|
6398
7035
|
),
|
|
6399
7036
|
minHitAt5: thresholds.minHitAt5 === void 0 ? void 0 : asPositiveNumber(thresholds.minHitAt5, `${sourceLabel}.thresholds.minHitAt5`),
|
|
6400
|
-
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`)
|
|
7037
|
+
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`),
|
|
7038
|
+
minRawDistinctTop3Ratio: thresholds.minRawDistinctTop3Ratio === void 0 ? void 0 : asPositiveNumber(
|
|
7039
|
+
thresholds.minRawDistinctTop3Ratio,
|
|
7040
|
+
`${sourceLabel}.thresholds.minRawDistinctTop3Ratio`
|
|
7041
|
+
)
|
|
6401
7042
|
}
|
|
6402
7043
|
};
|
|
6403
7044
|
}
|
|
@@ -6891,8 +7532,12 @@ async function handleEvalCommand(args, cwd) {
|
|
|
6891
7532
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
6892
7533
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
6893
7534
|
}
|
|
6894
|
-
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath)
|
|
6895
|
-
|
|
7535
|
+
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath), {
|
|
7536
|
+
allowLegacyDiversityMetrics: true
|
|
7537
|
+
});
|
|
7538
|
+
const baselineSummary = loadSummary(path8.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
7539
|
+
allowLegacyDiversityMetrics: true
|
|
7540
|
+
});
|
|
6896
7541
|
const comparison = compareSummaries(
|
|
6897
7542
|
currentSummary,
|
|
6898
7543
|
baselineSummary,
|
|
@@ -6926,16 +7571,13 @@ function formatDefinitionLookup(results, query) {
|
|
|
6926
7571
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
6927
7572
|
}
|
|
6928
7573
|
const formatted = results.map((r, idx) => {
|
|
6929
|
-
const
|
|
6930
|
-
return `${
|
|
7574
|
+
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}`;
|
|
7575
|
+
return `${header} (score: ${r.score.toFixed(2)})
|
|
6931
7576
|
\`\`\`
|
|
6932
7577
|
${truncateContent(r.content)}
|
|
6933
7578
|
\`\`\``;
|
|
6934
7579
|
});
|
|
6935
|
-
|
|
6936
|
-
return `${header}
|
|
6937
|
-
|
|
6938
|
-
${formatted.join("\n\n")}`;
|
|
7580
|
+
return formatted.join("\n\n");
|
|
6939
7581
|
}
|
|
6940
7582
|
|
|
6941
7583
|
// src/mcp-server.ts
|
|
@@ -7389,7 +8031,10 @@ Use the implementation_lookup tool to find where this symbol is defined. This pr
|
|
|
7389
8031
|
return server;
|
|
7390
8032
|
}
|
|
7391
8033
|
|
|
7392
|
-
// src/
|
|
8034
|
+
// src/config/merger.ts
|
|
8035
|
+
var import_fs10 = require("fs");
|
|
8036
|
+
var path9 = __toESM(require("path"), 1);
|
|
8037
|
+
var os4 = __toESM(require("os"), 1);
|
|
7393
8038
|
function loadJsonFile(filePath) {
|
|
7394
8039
|
try {
|
|
7395
8040
|
if ((0, import_fs10.existsSync)(filePath)) {
|
|
@@ -7400,25 +8045,101 @@ function loadJsonFile(filePath) {
|
|
|
7400
8045
|
}
|
|
7401
8046
|
return null;
|
|
7402
8047
|
}
|
|
7403
|
-
function
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
8048
|
+
function loadMergedConfig(projectRoot) {
|
|
8049
|
+
const globalConfigPath = path9.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8050
|
+
const globalConfig = loadJsonFile(globalConfigPath);
|
|
8051
|
+
const projectConfigPath = path9.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8052
|
+
const projectConfig = loadJsonFile(projectConfigPath);
|
|
8053
|
+
if (!globalConfig && !projectConfig) {
|
|
8054
|
+
return {};
|
|
8055
|
+
}
|
|
8056
|
+
if (!projectConfig && globalConfig) {
|
|
8057
|
+
return globalConfig;
|
|
8058
|
+
}
|
|
8059
|
+
if (!globalConfig && projectConfig) {
|
|
8060
|
+
return projectConfig;
|
|
8061
|
+
}
|
|
8062
|
+
const merged = { ...globalConfig };
|
|
8063
|
+
if (projectConfig && "embeddingProvider" in projectConfig) {
|
|
8064
|
+
merged.embeddingProvider = projectConfig.embeddingProvider;
|
|
8065
|
+
} else if (globalConfig && globalConfig.embeddingProvider) {
|
|
8066
|
+
merged.embeddingProvider = globalConfig.embeddingProvider;
|
|
8067
|
+
}
|
|
8068
|
+
if (projectConfig && "customProvider" in projectConfig) {
|
|
8069
|
+
merged.customProvider = projectConfig.customProvider;
|
|
8070
|
+
} else if (globalConfig && globalConfig.customProvider) {
|
|
8071
|
+
merged.customProvider = globalConfig.customProvider;
|
|
8072
|
+
}
|
|
8073
|
+
if (projectConfig && "embeddingModel" in projectConfig) {
|
|
8074
|
+
merged.embeddingModel = projectConfig.embeddingModel;
|
|
8075
|
+
} else if (globalConfig && globalConfig.embeddingModel) {
|
|
8076
|
+
merged.embeddingModel = globalConfig.embeddingModel;
|
|
8077
|
+
}
|
|
8078
|
+
if (projectConfig && "reranker" in projectConfig) {
|
|
8079
|
+
merged.reranker = projectConfig.reranker;
|
|
8080
|
+
} else if (globalConfig && globalConfig.reranker) {
|
|
8081
|
+
merged.reranker = globalConfig.reranker;
|
|
8082
|
+
}
|
|
8083
|
+
if (projectConfig && "include" in projectConfig) {
|
|
8084
|
+
merged.include = projectConfig.include;
|
|
8085
|
+
} else if (globalConfig && globalConfig.include) {
|
|
8086
|
+
merged.include = globalConfig.include;
|
|
8087
|
+
}
|
|
8088
|
+
if (projectConfig && "exclude" in projectConfig) {
|
|
8089
|
+
merged.exclude = projectConfig.exclude;
|
|
8090
|
+
} else if (globalConfig && globalConfig.exclude) {
|
|
8091
|
+
merged.exclude = globalConfig.exclude;
|
|
8092
|
+
}
|
|
8093
|
+
if (projectConfig && "indexing" in projectConfig) {
|
|
8094
|
+
merged.indexing = projectConfig.indexing;
|
|
8095
|
+
} else if (globalConfig && globalConfig.indexing) {
|
|
8096
|
+
merged.indexing = globalConfig.indexing;
|
|
8097
|
+
}
|
|
8098
|
+
if (projectConfig && "search" in projectConfig) {
|
|
8099
|
+
merged.search = projectConfig.search;
|
|
8100
|
+
} else if (globalConfig && globalConfig.search) {
|
|
8101
|
+
merged.search = globalConfig.search;
|
|
8102
|
+
}
|
|
8103
|
+
if (projectConfig && "debug" in projectConfig) {
|
|
8104
|
+
merged.debug = projectConfig.debug;
|
|
8105
|
+
} else if (globalConfig && globalConfig.debug) {
|
|
8106
|
+
merged.debug = globalConfig.debug;
|
|
8107
|
+
}
|
|
8108
|
+
if (projectConfig && "scope" in projectConfig) {
|
|
8109
|
+
merged.scope = projectConfig.scope;
|
|
8110
|
+
} else if (globalConfig && "scope" in globalConfig) {
|
|
8111
|
+
merged.scope = globalConfig.scope;
|
|
8112
|
+
}
|
|
8113
|
+
if (projectConfig) {
|
|
8114
|
+
for (const key of Object.keys(projectConfig)) {
|
|
8115
|
+
if (key === "embeddingProvider" || key === "customProvider" || key === "embeddingModel" || key === "reranker" || key === "include" || key === "exclude" || key === "indexing" || key === "search" || key === "debug" || key === "scope" || key === "knowledgeBases" || key === "additionalInclude") {
|
|
8116
|
+
continue;
|
|
8117
|
+
}
|
|
8118
|
+
merged[key] = projectConfig[key];
|
|
8119
|
+
}
|
|
7407
8120
|
}
|
|
7408
|
-
const
|
|
7409
|
-
|
|
7410
|
-
const
|
|
7411
|
-
|
|
7412
|
-
|
|
8121
|
+
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
|
|
8122
|
+
const projectKbs = projectConfig && Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
|
|
8123
|
+
const allKbs = [...globalKbs, ...projectKbs];
|
|
8124
|
+
const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
|
|
8125
|
+
merged.knowledgeBases = uniqueKbs;
|
|
8126
|
+
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
|
|
8127
|
+
const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
|
|
8128
|
+
const allAdditional = [...globalAdditional, ...projectAdditional];
|
|
8129
|
+
const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
|
|
8130
|
+
merged.additionalInclude = uniqueAdditional;
|
|
8131
|
+
return merged;
|
|
7413
8132
|
}
|
|
8133
|
+
|
|
8134
|
+
// src/cli.ts
|
|
7414
8135
|
function parseArgs(argv) {
|
|
7415
8136
|
let project = process.cwd();
|
|
7416
8137
|
let config;
|
|
7417
8138
|
for (let i = 2; i < argv.length; i++) {
|
|
7418
8139
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
7419
|
-
project =
|
|
8140
|
+
project = path10.resolve(argv[++i]);
|
|
7420
8141
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
7421
|
-
config =
|
|
8142
|
+
config = path10.resolve(argv[++i]);
|
|
7422
8143
|
}
|
|
7423
8144
|
}
|
|
7424
8145
|
return { project, config };
|
|
@@ -7429,7 +8150,7 @@ async function main() {
|
|
|
7429
8150
|
process.exit(exitCode);
|
|
7430
8151
|
}
|
|
7431
8152
|
const args = parseArgs(process.argv);
|
|
7432
|
-
const rawConfig =
|
|
8153
|
+
const rawConfig = loadMergedConfig(args.project);
|
|
7433
8154
|
const config = parseConfig(rawConfig);
|
|
7434
8155
|
const server = createMcpServer(args.project, config);
|
|
7435
8156
|
const transport = new import_stdio.StdioServerTransport();
|