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.js
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
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
652
|
-
import
|
|
653
|
-
import * as path9 from "path";
|
|
654
|
-
import * as os4 from "os";
|
|
652
|
+
import * as path10 from "path";
|
|
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
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1000
1060
|
import * as path from "path";
|
|
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 = 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
|
import { existsSync as existsSync2, readFileSync as readFileSync3, promises as fsPromises } from "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 fsPromises.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 fsPromises.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 fsPromises.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`);
|
|
@@ -3186,7 +3480,52 @@ function getNativeBinding() {
|
|
|
3186
3480
|
const require2 = module.createRequire(requireTarget);
|
|
3187
3481
|
return require2(nativePath);
|
|
3188
3482
|
}
|
|
3189
|
-
|
|
3483
|
+
function createMockNativeBinding() {
|
|
3484
|
+
const error = new Error("Native module not available. Please rebuild with 'npm run build:native'.");
|
|
3485
|
+
return {
|
|
3486
|
+
parseFile: () => {
|
|
3487
|
+
throw error;
|
|
3488
|
+
},
|
|
3489
|
+
parseFiles: () => {
|
|
3490
|
+
throw error;
|
|
3491
|
+
},
|
|
3492
|
+
hashContent: () => {
|
|
3493
|
+
throw error;
|
|
3494
|
+
},
|
|
3495
|
+
hashFile: () => {
|
|
3496
|
+
throw error;
|
|
3497
|
+
},
|
|
3498
|
+
extractCalls: () => {
|
|
3499
|
+
throw error;
|
|
3500
|
+
},
|
|
3501
|
+
VectorStore: class {
|
|
3502
|
+
constructor() {
|
|
3503
|
+
throw error;
|
|
3504
|
+
}
|
|
3505
|
+
},
|
|
3506
|
+
InvertedIndex: class {
|
|
3507
|
+
constructor() {
|
|
3508
|
+
throw error;
|
|
3509
|
+
}
|
|
3510
|
+
},
|
|
3511
|
+
Database: class {
|
|
3512
|
+
constructor() {
|
|
3513
|
+
throw error;
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
var native;
|
|
3519
|
+
try {
|
|
3520
|
+
native = getNativeBinding();
|
|
3521
|
+
} catch (e) {
|
|
3522
|
+
console.error("[codebase-index] Failed to load native module:", e);
|
|
3523
|
+
native = createMockNativeBinding();
|
|
3524
|
+
}
|
|
3525
|
+
function parseFileAsText(filePath, content) {
|
|
3526
|
+
const result = native.parseFileAsText(filePath, content);
|
|
3527
|
+
return result.map(mapChunk);
|
|
3528
|
+
}
|
|
3190
3529
|
function parseFiles(files) {
|
|
3191
3530
|
const result = native.parseFiles(files);
|
|
3192
3531
|
return result.map((f) => ({
|
|
@@ -4012,6 +4351,32 @@ function isLikelyImplementationPath(filePath) {
|
|
|
4012
4351
|
}
|
|
4013
4352
|
return true;
|
|
4014
4353
|
}
|
|
4354
|
+
function isDocumentationPath(filePath) {
|
|
4355
|
+
const lowered = filePath.toLowerCase();
|
|
4356
|
+
const ext = lowered.split(".").pop() ?? "";
|
|
4357
|
+
return lowered.includes("readme") || ["md", "mdx", "rst", "adoc", "txt"].includes(ext);
|
|
4358
|
+
}
|
|
4359
|
+
function classifyExternalRerankBand(candidate, preferSourcePaths, docIntent) {
|
|
4360
|
+
const isDocOrTest = isTestOrDocPath(candidate.metadata.filePath);
|
|
4361
|
+
const isDocumentation = isDocumentationPath(candidate.metadata.filePath);
|
|
4362
|
+
const isImplementation = isLikelyImplementationPath(candidate.metadata.filePath) && isImplementationChunkType(candidate.metadata.chunkType);
|
|
4363
|
+
if (preferSourcePaths) {
|
|
4364
|
+
if (isImplementation) return "implementation";
|
|
4365
|
+
if (isDocumentation) return "documentation";
|
|
4366
|
+
if (isDocOrTest) return "test";
|
|
4367
|
+
return "other";
|
|
4368
|
+
}
|
|
4369
|
+
if (docIntent) {
|
|
4370
|
+
if (isDocumentation) return "documentation";
|
|
4371
|
+
if (isImplementation) return "implementation";
|
|
4372
|
+
if (isDocOrTest) return "test";
|
|
4373
|
+
return "other";
|
|
4374
|
+
}
|
|
4375
|
+
if (isImplementation) return "implementation";
|
|
4376
|
+
if (isDocumentation) return "documentation";
|
|
4377
|
+
if (isDocOrTest) return "test";
|
|
4378
|
+
return "other";
|
|
4379
|
+
}
|
|
4015
4380
|
function classifyQueryIntent(tokens) {
|
|
4016
4381
|
const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
|
|
4017
4382
|
const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
|
|
@@ -4386,8 +4751,74 @@ function rerankResults(query, candidates, rerankTopN, options) {
|
|
|
4386
4751
|
return 0;
|
|
4387
4752
|
});
|
|
4388
4753
|
}
|
|
4754
|
+
const shouldDiversify = !(preferSourcePaths && identifierHints.length > 0);
|
|
4755
|
+
const diversifiedHead = diversifyEntriesByFileAndSymbol(head, (entry) => entry.candidate, shouldDiversify);
|
|
4389
4756
|
const tail = candidates.slice(topN);
|
|
4390
|
-
return [...
|
|
4757
|
+
return [...diversifiedHead.map((entry) => entry.candidate), ...tail];
|
|
4758
|
+
}
|
|
4759
|
+
function diversifyEntriesByFileAndSymbol(entries, getCandidate, enabled) {
|
|
4760
|
+
if (!enabled || entries.length <= 2) {
|
|
4761
|
+
return entries;
|
|
4762
|
+
}
|
|
4763
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4764
|
+
const groupOrder = [];
|
|
4765
|
+
for (const entry of entries) {
|
|
4766
|
+
const candidate = getCandidate(entry);
|
|
4767
|
+
const filePath = candidate.metadata.filePath;
|
|
4768
|
+
if (!groups.has(filePath)) {
|
|
4769
|
+
groups.set(filePath, []);
|
|
4770
|
+
groupOrder.push(filePath);
|
|
4771
|
+
}
|
|
4772
|
+
groups.get(filePath)?.push(entry);
|
|
4773
|
+
}
|
|
4774
|
+
const diversifiedGroups = groupOrder.map((filePath) => {
|
|
4775
|
+
const group = groups.get(filePath) ?? [];
|
|
4776
|
+
return diversifyGroupBySymbol(group, getCandidate);
|
|
4777
|
+
});
|
|
4778
|
+
const result = [];
|
|
4779
|
+
let added = true;
|
|
4780
|
+
let round = 0;
|
|
4781
|
+
while (added) {
|
|
4782
|
+
added = false;
|
|
4783
|
+
for (const group of diversifiedGroups) {
|
|
4784
|
+
const entry = group[round];
|
|
4785
|
+
if (entry !== void 0) {
|
|
4786
|
+
result.push(entry);
|
|
4787
|
+
added = true;
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
round += 1;
|
|
4791
|
+
}
|
|
4792
|
+
return result;
|
|
4793
|
+
}
|
|
4794
|
+
function diversifyCandidatesByFile(candidates, enabled) {
|
|
4795
|
+
return diversifyEntriesByFileAndSymbol(candidates, (candidate) => candidate, enabled);
|
|
4796
|
+
}
|
|
4797
|
+
function diversifyGroupBySymbol(entries, getCandidate) {
|
|
4798
|
+
if (entries.length <= 2) {
|
|
4799
|
+
return entries;
|
|
4800
|
+
}
|
|
4801
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
4802
|
+
const primary = [];
|
|
4803
|
+
const remainder = [];
|
|
4804
|
+
for (const entry of entries) {
|
|
4805
|
+
const key = buildDiversityKey(getCandidate(entry).metadata);
|
|
4806
|
+
if (!seenKeys.has(key)) {
|
|
4807
|
+
seenKeys.add(key);
|
|
4808
|
+
primary.push(entry);
|
|
4809
|
+
} else {
|
|
4810
|
+
remainder.push(entry);
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
return [...primary, ...remainder];
|
|
4814
|
+
}
|
|
4815
|
+
function buildDiversityKey(metadata) {
|
|
4816
|
+
const normalizedPath = metadata.filePath.toLowerCase();
|
|
4817
|
+
const normalizedName = (metadata.name ?? "").trim().toLowerCase();
|
|
4818
|
+
if (normalizedName.length > 0) {
|
|
4819
|
+
return `${normalizedPath}#${normalizedName}`;
|
|
4820
|
+
}
|
|
4821
|
+
return normalizedPath;
|
|
4391
4822
|
}
|
|
4392
4823
|
function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
4393
4824
|
const overfetchLimit = Math.max(options.limit * 4, options.limit);
|
|
@@ -4715,6 +5146,7 @@ var Indexer = class {
|
|
|
4715
5146
|
database = null;
|
|
4716
5147
|
provider = null;
|
|
4717
5148
|
configuredProviderInfo = null;
|
|
5149
|
+
reranker = null;
|
|
4718
5150
|
fileHashCache = /* @__PURE__ */ new Map();
|
|
4719
5151
|
fileHashCachePath = "";
|
|
4720
5152
|
failedBatchesPath = "";
|
|
@@ -4844,6 +5276,152 @@ var Indexer = class {
|
|
|
4844
5276
|
return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
|
|
4845
5277
|
}
|
|
4846
5278
|
}
|
|
5279
|
+
async rerankCandidatesWithApi(query, candidates, options) {
|
|
5280
|
+
const reranker = this.config.reranker;
|
|
5281
|
+
if (!reranker || !reranker.enabled || candidates.length <= 1) {
|
|
5282
|
+
return candidates;
|
|
5283
|
+
}
|
|
5284
|
+
const queryTokens = Array.from(tokenizeTextForRanking(query));
|
|
5285
|
+
const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
|
|
5286
|
+
const docIntent = classifyDocIntent(queryTokens) === "docs";
|
|
5287
|
+
if (options?.definitionIntent === true) {
|
|
5288
|
+
return candidates;
|
|
5289
|
+
}
|
|
5290
|
+
if (options?.hasIdentifierHints === true && preferSourcePaths && !docIntent) {
|
|
5291
|
+
return candidates;
|
|
5292
|
+
}
|
|
5293
|
+
const topN = Math.min(reranker.topN, candidates.length);
|
|
5294
|
+
const head = candidates.slice(0, topN);
|
|
5295
|
+
const tail = candidates.slice(topN);
|
|
5296
|
+
const grouped = /* @__PURE__ */ new Map([
|
|
5297
|
+
["implementation", []],
|
|
5298
|
+
["documentation", []],
|
|
5299
|
+
["test", []],
|
|
5300
|
+
["other", []]
|
|
5301
|
+
]);
|
|
5302
|
+
for (const candidate of head) {
|
|
5303
|
+
const band = classifyExternalRerankBand(candidate, preferSourcePaths, docIntent);
|
|
5304
|
+
grouped.get(band)?.push(candidate);
|
|
5305
|
+
}
|
|
5306
|
+
const orderedBands = preferSourcePaths ? ["implementation", "other", "documentation", "test"] : docIntent ? ["documentation", "implementation", "other", "test"] : ["implementation", "other", "documentation", "test"];
|
|
5307
|
+
try {
|
|
5308
|
+
const rerankedHead = [];
|
|
5309
|
+
for (const band of orderedBands) {
|
|
5310
|
+
const bandCandidates = grouped.get(band) ?? [];
|
|
5311
|
+
if (bandCandidates.length <= 1) {
|
|
5312
|
+
rerankedHead.push(...bandCandidates);
|
|
5313
|
+
continue;
|
|
5314
|
+
}
|
|
5315
|
+
const documents = await Promise.all(
|
|
5316
|
+
bandCandidates.map(async (candidate) => ({
|
|
5317
|
+
id: candidate.id,
|
|
5318
|
+
text: await this.createRerankerDocumentText(candidate)
|
|
5319
|
+
}))
|
|
5320
|
+
);
|
|
5321
|
+
const rankedIds = await this.callExternalReranker(query, documents, reranker);
|
|
5322
|
+
if (rankedIds.length === 0) {
|
|
5323
|
+
rerankedHead.push(...bandCandidates);
|
|
5324
|
+
continue;
|
|
5325
|
+
}
|
|
5326
|
+
const order = new Map(rankedIds.map((id, index) => [id, index]));
|
|
5327
|
+
const bandReranked = [...bandCandidates].sort((a, b) => {
|
|
5328
|
+
const aRank = order.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
5329
|
+
const bRank = order.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
5330
|
+
if (aRank !== bRank) {
|
|
5331
|
+
return aRank - bRank;
|
|
5332
|
+
}
|
|
5333
|
+
if (b.score !== a.score) {
|
|
5334
|
+
return b.score - a.score;
|
|
5335
|
+
}
|
|
5336
|
+
return a.id.localeCompare(b.id);
|
|
5337
|
+
});
|
|
5338
|
+
const shouldDiversifyBand = !options?.hasIdentifierHints;
|
|
5339
|
+
rerankedHead.push(...diversifyCandidatesByFile(bandReranked, shouldDiversifyBand));
|
|
5340
|
+
}
|
|
5341
|
+
this.logger.search("debug", "Applied external reranker", {
|
|
5342
|
+
provider: reranker.provider,
|
|
5343
|
+
model: reranker.model,
|
|
5344
|
+
candidateCount: head.length,
|
|
5345
|
+
bands: orderedBands
|
|
5346
|
+
});
|
|
5347
|
+
return [...rerankedHead, ...tail];
|
|
5348
|
+
} catch (error) {
|
|
5349
|
+
this.logger.search("warn", "External reranker failed; using deterministic order", {
|
|
5350
|
+
provider: reranker.provider,
|
|
5351
|
+
model: reranker.model,
|
|
5352
|
+
error: getErrorMessage(error)
|
|
5353
|
+
});
|
|
5354
|
+
return candidates;
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
async callExternalReranker(query, documents, reranker) {
|
|
5358
|
+
const headers = {
|
|
5359
|
+
"Content-Type": "application/json"
|
|
5360
|
+
};
|
|
5361
|
+
if (reranker.apiKey) {
|
|
5362
|
+
headers.Authorization = `Bearer ${reranker.apiKey}`;
|
|
5363
|
+
}
|
|
5364
|
+
const controller = new AbortController();
|
|
5365
|
+
const timeout = setTimeout(() => controller.abort(), reranker.timeoutMs);
|
|
5366
|
+
try {
|
|
5367
|
+
const response = await fetch(`${reranker.baseUrl}/rerank`, {
|
|
5368
|
+
method: "POST",
|
|
5369
|
+
headers,
|
|
5370
|
+
body: JSON.stringify({
|
|
5371
|
+
model: reranker.model,
|
|
5372
|
+
query,
|
|
5373
|
+
documents: documents.map((document) => document.text),
|
|
5374
|
+
top_n: documents.length,
|
|
5375
|
+
return_documents: false
|
|
5376
|
+
}),
|
|
5377
|
+
signal: controller.signal
|
|
5378
|
+
});
|
|
5379
|
+
if (!response.ok) {
|
|
5380
|
+
throw new Error(`Reranker API error: ${response.status} - ${await response.text()}`);
|
|
5381
|
+
}
|
|
5382
|
+
const body = await response.json();
|
|
5383
|
+
if (!Array.isArray(body.results)) {
|
|
5384
|
+
throw new Error("Reranker API returned unexpected response format.");
|
|
5385
|
+
}
|
|
5386
|
+
return body.results.map((result) => {
|
|
5387
|
+
const index = typeof result.index === "number" ? result.index : -1;
|
|
5388
|
+
return documents[index]?.id;
|
|
5389
|
+
}).filter((id) => typeof id === "string");
|
|
5390
|
+
} catch (error) {
|
|
5391
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
5392
|
+
throw new Error(`Reranker request timed out after ${reranker.timeoutMs}ms`);
|
|
5393
|
+
}
|
|
5394
|
+
throw error;
|
|
5395
|
+
} finally {
|
|
5396
|
+
clearTimeout(timeout);
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
async createRerankerDocumentText(candidate) {
|
|
5400
|
+
const parts = [
|
|
5401
|
+
`path: ${candidate.metadata.filePath}`,
|
|
5402
|
+
`chunk_type: ${candidate.metadata.chunkType}`,
|
|
5403
|
+
`language: ${candidate.metadata.language}`,
|
|
5404
|
+
`lines: ${candidate.metadata.startLine}-${candidate.metadata.endLine}`
|
|
5405
|
+
];
|
|
5406
|
+
if (candidate.metadata.name) {
|
|
5407
|
+
parts.push(`name: ${candidate.metadata.name}`);
|
|
5408
|
+
}
|
|
5409
|
+
const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
|
|
5410
|
+
parts.push(`intent_hint: ${intent}`);
|
|
5411
|
+
try {
|
|
5412
|
+
const fileContent = await fsPromises2.readFile(candidate.metadata.filePath, "utf-8");
|
|
5413
|
+
const lines = fileContent.split("\n");
|
|
5414
|
+
const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
|
|
5415
|
+
const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
|
|
5416
|
+
const snippet = lines.slice(snippetStartLine - 1, snippetEndLine).join("\n").trim();
|
|
5417
|
+
parts.push("snippet:");
|
|
5418
|
+
parts.push(snippet.length > 0 ? snippet : "[empty]");
|
|
5419
|
+
} catch {
|
|
5420
|
+
parts.push("snippet:");
|
|
5421
|
+
parts.push("[unavailable]");
|
|
5422
|
+
}
|
|
5423
|
+
return parts.join("\n");
|
|
5424
|
+
}
|
|
4847
5425
|
async initialize() {
|
|
4848
5426
|
if (this.config.embeddingProvider === "custom") {
|
|
4849
5427
|
if (!this.config.customProvider) {
|
|
@@ -4863,9 +5441,19 @@ var Indexer = class {
|
|
|
4863
5441
|
this.logger.info("Initializing indexer", {
|
|
4864
5442
|
provider: this.configuredProviderInfo.provider,
|
|
4865
5443
|
model: this.configuredProviderInfo.modelInfo.model,
|
|
4866
|
-
scope: this.config.scope
|
|
5444
|
+
scope: this.config.scope,
|
|
5445
|
+
rerankerEnabled: this.config.reranker?.enabled ?? false
|
|
4867
5446
|
});
|
|
4868
5447
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
5448
|
+
if (this.config.reranker?.enabled) {
|
|
5449
|
+
this.reranker = createReranker(this.config.reranker);
|
|
5450
|
+
if (this.reranker.isAvailable()) {
|
|
5451
|
+
this.logger.info("Reranker initialized", {
|
|
5452
|
+
model: this.config.reranker.model,
|
|
5453
|
+
baseUrl: this.config.reranker.baseUrl
|
|
5454
|
+
});
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
4869
5457
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
4870
5458
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
4871
5459
|
const storePath = path6.join(this.indexPath, "vectors");
|
|
@@ -5055,11 +5643,14 @@ var Indexer = class {
|
|
|
5055
5643
|
}
|
|
5056
5644
|
async estimateCost() {
|
|
5057
5645
|
const { configuredProviderInfo } = await this.ensureInitialized();
|
|
5646
|
+
const includePatterns = [...this.config.include, ...this.config.additionalInclude];
|
|
5058
5647
|
const { files } = await collectFiles(
|
|
5059
5648
|
this.projectRoot,
|
|
5060
|
-
|
|
5649
|
+
includePatterns,
|
|
5061
5650
|
this.config.exclude,
|
|
5062
|
-
this.config.indexing.maxFileSize
|
|
5651
|
+
this.config.indexing.maxFileSize,
|
|
5652
|
+
this.config.knowledgeBases,
|
|
5653
|
+
{ maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
|
|
5063
5654
|
);
|
|
5064
5655
|
return createCostEstimate(files, configuredProviderInfo);
|
|
5065
5656
|
}
|
|
@@ -5094,11 +5685,14 @@ var Indexer = class {
|
|
|
5094
5685
|
totalChunks: 0
|
|
5095
5686
|
});
|
|
5096
5687
|
this.loadFileHashCache();
|
|
5688
|
+
const includePatterns = [...this.config.include, ...this.config.additionalInclude];
|
|
5097
5689
|
const { files, skipped } = await collectFiles(
|
|
5098
5690
|
this.projectRoot,
|
|
5099
|
-
|
|
5691
|
+
includePatterns,
|
|
5100
5692
|
this.config.exclude,
|
|
5101
|
-
this.config.indexing.maxFileSize
|
|
5693
|
+
this.config.indexing.maxFileSize,
|
|
5694
|
+
this.config.knowledgeBases,
|
|
5695
|
+
{ maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
|
|
5102
5696
|
);
|
|
5103
5697
|
stats.totalFiles = files.length;
|
|
5104
5698
|
stats.skippedFiles = skipped;
|
|
@@ -5167,7 +5761,15 @@ var Indexer = class {
|
|
|
5167
5761
|
stats.parseFailures.push(relativePath);
|
|
5168
5762
|
}
|
|
5169
5763
|
let fileChunkCount = 0;
|
|
5170
|
-
|
|
5764
|
+
let chunksToProcess = parsed.chunks;
|
|
5765
|
+
if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
|
|
5766
|
+
const changedFile = changedFiles.find((f) => f.path === parsed.path);
|
|
5767
|
+
if (changedFile) {
|
|
5768
|
+
const textChunks = parseFileAsText(parsed.path, changedFile.content);
|
|
5769
|
+
chunksToProcess = textChunks;
|
|
5770
|
+
}
|
|
5771
|
+
}
|
|
5772
|
+
for (const chunk of chunksToProcess) {
|
|
5171
5773
|
if (fileChunkCount >= this.config.indexing.maxChunksPerFile) {
|
|
5172
5774
|
break;
|
|
5173
5775
|
}
|
|
@@ -5374,7 +5976,7 @@ var Indexer = class {
|
|
|
5374
5976
|
for (const batch of dynamicBatches) {
|
|
5375
5977
|
queue.add(async () => {
|
|
5376
5978
|
if (rateLimitBackoffMs > 0) {
|
|
5377
|
-
await new Promise((
|
|
5979
|
+
await new Promise((resolve6) => setTimeout(resolve6, rateLimitBackoffMs));
|
|
5378
5980
|
}
|
|
5379
5981
|
try {
|
|
5380
5982
|
const result = await pRetry(
|
|
@@ -5579,6 +6181,7 @@ var Indexer = class {
|
|
|
5579
6181
|
const rerankTopN = this.config.search.rerankTopN;
|
|
5580
6182
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5581
6183
|
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
6184
|
+
const identifierHints = extractIdentifierHints(query);
|
|
5582
6185
|
this.logger.search("debug", "Starting search", {
|
|
5583
6186
|
query,
|
|
5584
6187
|
maxResults,
|
|
@@ -5633,10 +6236,14 @@ var Indexer = class {
|
|
|
5633
6236
|
hybridWeight,
|
|
5634
6237
|
prioritizeSourcePaths: sourceIntent
|
|
5635
6238
|
});
|
|
6239
|
+
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
6240
|
+
definitionIntent: options?.definitionIntent === true,
|
|
6241
|
+
hasIdentifierHints: identifierHints.length > 0
|
|
6242
|
+
});
|
|
5636
6243
|
const fusionMs = performance2.now() - fusionStartTime;
|
|
5637
6244
|
const rescued = promoteIdentifierMatches(
|
|
5638
6245
|
query,
|
|
5639
|
-
|
|
6246
|
+
rerankedCombined,
|
|
5640
6247
|
semanticCandidates,
|
|
5641
6248
|
keywordCandidates,
|
|
5642
6249
|
database,
|
|
@@ -5667,7 +6274,7 @@ var Indexer = class {
|
|
|
5667
6274
|
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5668
6275
|
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5669
6276
|
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5670
|
-
const hasCodeHints = extractCodeTermHints(query).length > 0 ||
|
|
6277
|
+
const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
|
|
5671
6278
|
const baseFiltered = tiered.filter((r) => {
|
|
5672
6279
|
if (r.score < this.config.search.minScore) return false;
|
|
5673
6280
|
if (options?.fileType) {
|
|
@@ -5687,6 +6294,7 @@ var Indexer = class {
|
|
|
5687
6294
|
(r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
|
|
5688
6295
|
);
|
|
5689
6296
|
const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
|
|
6297
|
+
const finalResults = filtered;
|
|
5690
6298
|
const totalSearchMs = performance2.now() - searchStartTime;
|
|
5691
6299
|
this.logger.recordSearch(totalSearchMs, {
|
|
5692
6300
|
embeddingMs,
|
|
@@ -5696,7 +6304,7 @@ var Indexer = class {
|
|
|
5696
6304
|
});
|
|
5697
6305
|
this.logger.search("info", "Search complete", {
|
|
5698
6306
|
query,
|
|
5699
|
-
results:
|
|
6307
|
+
results: finalResults.length,
|
|
5700
6308
|
totalMs: Math.round(totalSearchMs * 100) / 100,
|
|
5701
6309
|
embeddingMs: Math.round(embeddingMs * 100) / 100,
|
|
5702
6310
|
vectorMs: Math.round(vectorMs * 100) / 100,
|
|
@@ -5706,7 +6314,7 @@ var Indexer = class {
|
|
|
5706
6314
|
});
|
|
5707
6315
|
const metadataOnly = options?.metadataOnly ?? false;
|
|
5708
6316
|
return Promise.all(
|
|
5709
|
-
|
|
6317
|
+
finalResults.map(async (r) => {
|
|
5710
6318
|
let content = "";
|
|
5711
6319
|
let contextStartLine = r.metadata.startLine;
|
|
5712
6320
|
let contextEndLine = r.metadata.endLine;
|
|
@@ -6040,6 +6648,12 @@ function evaluateBudgetGate(budget, summary, comparison) {
|
|
|
6040
6648
|
message: `MRR@10 ${summary.metrics.mrrAt10.toFixed(4)} is below minimum ${thresholds.minMrrAt10.toFixed(4)}`
|
|
6041
6649
|
});
|
|
6042
6650
|
}
|
|
6651
|
+
if (thresholds.minRawDistinctTop3Ratio !== void 0 && summary.metrics.rawDistinctTop3Ratio < thresholds.minRawDistinctTop3Ratio) {
|
|
6652
|
+
violations.push({
|
|
6653
|
+
metric: "minRawDistinctTop3Ratio",
|
|
6654
|
+
message: `Raw Distinct Top@3 ${summary.metrics.rawDistinctTop3Ratio.toFixed(4)} is below minimum ${thresholds.minRawDistinctTop3Ratio.toFixed(4)}`
|
|
6655
|
+
});
|
|
6656
|
+
}
|
|
6043
6657
|
if (comparison) {
|
|
6044
6658
|
if (thresholds.hitAt5MaxDrop !== void 0 && comparison.deltas.hitAt5.absolute < -thresholds.hitAt5MaxDrop) {
|
|
6045
6659
|
violations.push({
|
|
@@ -6053,6 +6667,12 @@ function evaluateBudgetGate(budget, summary, comparison) {
|
|
|
6053
6667
|
message: `MRR@10 drop ${comparison.deltas.mrrAt10.absolute.toFixed(4)} exceeds allowed -${thresholds.mrrAt10MaxDrop.toFixed(4)}`
|
|
6054
6668
|
});
|
|
6055
6669
|
}
|
|
6670
|
+
if (thresholds.rawDistinctTop3RatioMaxDrop !== void 0 && comparison.deltas.rawDistinctTop3Ratio.absolute < -thresholds.rawDistinctTop3RatioMaxDrop) {
|
|
6671
|
+
violations.push({
|
|
6672
|
+
metric: "rawDistinctTop3RatioMaxDrop",
|
|
6673
|
+
message: `Raw Distinct Top@3 drop ${comparison.deltas.rawDistinctTop3Ratio.absolute.toFixed(4)} exceeds allowed -${thresholds.rawDistinctTop3RatioMaxDrop.toFixed(4)}`
|
|
6674
|
+
});
|
|
6675
|
+
}
|
|
6056
6676
|
if (thresholds.p95LatencyMaxMultiplier !== void 0) {
|
|
6057
6677
|
const baselineP95 = comparison.deltas.latencyP95Ms.baseline;
|
|
6058
6678
|
if (baselineP95 > BASELINE_P95_EPSILON_MS) {
|
|
@@ -6107,6 +6727,12 @@ function uniqueResultsByPath(results) {
|
|
|
6107
6727
|
}
|
|
6108
6728
|
return unique;
|
|
6109
6729
|
}
|
|
6730
|
+
function distinctTopKRatio(results, k) {
|
|
6731
|
+
const top = results.slice(0, k);
|
|
6732
|
+
if (top.length === 0) return 0;
|
|
6733
|
+
const distinct = new Set(top.map((result) => normalizePath(result.filePath))).size;
|
|
6734
|
+
return distinct / top.length;
|
|
6735
|
+
}
|
|
6110
6736
|
function pathMatchesExpected(actualPath, expectedPath) {
|
|
6111
6737
|
const actual = normalizePath(actualPath);
|
|
6112
6738
|
const expected = normalizePath(expectedPath);
|
|
@@ -6185,6 +6811,7 @@ function buildPerQueryResult(query, results, latencyMs, k) {
|
|
|
6185
6811
|
reciprocalRankAt10: reciprocalRankAtK(deduped, relevantPaths, 10),
|
|
6186
6812
|
ndcgAt10: ndcgAtK(deduped, relevantPaths, 10),
|
|
6187
6813
|
failureBucket: classifyFailureBucket(query, results, k),
|
|
6814
|
+
rawTop3DistinctRatio: distinctTopKRatio(results, 3),
|
|
6188
6815
|
results: deduped
|
|
6189
6816
|
};
|
|
6190
6817
|
return perQuery;
|
|
@@ -6198,7 +6825,9 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6198
6825
|
hitAt5: 0,
|
|
6199
6826
|
hitAt10: 0,
|
|
6200
6827
|
mrrAt10: 0,
|
|
6201
|
-
ndcgAt10: 0
|
|
6828
|
+
ndcgAt10: 0,
|
|
6829
|
+
distinctTop3Ratio: 0,
|
|
6830
|
+
rawDistinctTop3Ratio: 0
|
|
6202
6831
|
};
|
|
6203
6832
|
const failureBuckets = {
|
|
6204
6833
|
"wrong-file": 0,
|
|
@@ -6214,6 +6843,8 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6214
6843
|
if (query.hitAt10) sum.hitAt10 += 1;
|
|
6215
6844
|
sum.mrrAt10 += query.reciprocalRankAt10;
|
|
6216
6845
|
sum.ndcgAt10 += query.ndcgAt10;
|
|
6846
|
+
sum.distinctTop3Ratio += distinctTopKRatio(query.results, 3);
|
|
6847
|
+
sum.rawDistinctTop3Ratio += query.rawTop3DistinctRatio;
|
|
6217
6848
|
if (query.failureBucket) {
|
|
6218
6849
|
failureBuckets[query.failureBucket] += 1;
|
|
6219
6850
|
}
|
|
@@ -6226,6 +6857,8 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
6226
6857
|
hitAt10: safeDiv(sum.hitAt10),
|
|
6227
6858
|
mrrAt10: safeDiv(sum.mrrAt10),
|
|
6228
6859
|
ndcgAt10: safeDiv(sum.ndcgAt10),
|
|
6860
|
+
distinctTop3Ratio: safeDiv(sum.distinctTop3Ratio),
|
|
6861
|
+
rawDistinctTop3Ratio: safeDiv(sum.rawDistinctTop3Ratio),
|
|
6229
6862
|
latencyMs: {
|
|
6230
6863
|
p50: percentile(latencies, 0.5),
|
|
6231
6864
|
p95: percentile(latencies, 0.95),
|
|
@@ -6256,23 +6889,23 @@ function isRecord(value) {
|
|
|
6256
6889
|
function isStringArray2(value) {
|
|
6257
6890
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6258
6891
|
}
|
|
6259
|
-
function asPositiveNumber(value,
|
|
6892
|
+
function asPositiveNumber(value, path11) {
|
|
6260
6893
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
6261
|
-
throw new Error(`${
|
|
6894
|
+
throw new Error(`${path11} must be a non-negative number`);
|
|
6262
6895
|
}
|
|
6263
6896
|
return value;
|
|
6264
6897
|
}
|
|
6265
|
-
function parseQueryType(value,
|
|
6898
|
+
function parseQueryType(value, path11) {
|
|
6266
6899
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
6267
6900
|
return value;
|
|
6268
6901
|
}
|
|
6269
6902
|
throw new Error(
|
|
6270
|
-
`${
|
|
6903
|
+
`${path11} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
6271
6904
|
);
|
|
6272
6905
|
}
|
|
6273
|
-
function parseExpected(input,
|
|
6906
|
+
function parseExpected(input, path11) {
|
|
6274
6907
|
if (!isRecord(input)) {
|
|
6275
|
-
throw new Error(`${
|
|
6908
|
+
throw new Error(`${path11} must be an object`);
|
|
6276
6909
|
}
|
|
6277
6910
|
const filePathRaw = input.filePath;
|
|
6278
6911
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -6281,16 +6914,16 @@ function parseExpected(input, path10) {
|
|
|
6281
6914
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
6282
6915
|
const acceptableFiles = isStringArray2(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
6283
6916
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
6284
|
-
throw new Error(`${
|
|
6917
|
+
throw new Error(`${path11} must include either expected.filePath or expected.acceptableFiles`);
|
|
6285
6918
|
}
|
|
6286
6919
|
if (acceptableFilesRaw !== void 0 && !isStringArray2(acceptableFilesRaw)) {
|
|
6287
|
-
throw new Error(`${
|
|
6920
|
+
throw new Error(`${path11}.acceptableFiles must be an array of strings`);
|
|
6288
6921
|
}
|
|
6289
6922
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
6290
|
-
throw new Error(`${
|
|
6923
|
+
throw new Error(`${path11}.symbol must be a string when provided`);
|
|
6291
6924
|
}
|
|
6292
6925
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
6293
|
-
throw new Error(`${
|
|
6926
|
+
throw new Error(`${path11}.branch must be a string when provided`);
|
|
6294
6927
|
}
|
|
6295
6928
|
return {
|
|
6296
6929
|
filePath,
|
|
@@ -6300,25 +6933,25 @@ function parseExpected(input, path10) {
|
|
|
6300
6933
|
};
|
|
6301
6934
|
}
|
|
6302
6935
|
function parseQuery(input, index) {
|
|
6303
|
-
const
|
|
6936
|
+
const path11 = `queries[${index}]`;
|
|
6304
6937
|
if (!isRecord(input)) {
|
|
6305
|
-
throw new Error(`${
|
|
6938
|
+
throw new Error(`${path11} must be an object`);
|
|
6306
6939
|
}
|
|
6307
6940
|
const id = input.id;
|
|
6308
6941
|
const query = input.query;
|
|
6309
6942
|
const queryType = input.queryType;
|
|
6310
6943
|
const expected = input.expected;
|
|
6311
6944
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
6312
|
-
throw new Error(`${
|
|
6945
|
+
throw new Error(`${path11}.id must be a non-empty string`);
|
|
6313
6946
|
}
|
|
6314
6947
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
6315
|
-
throw new Error(`${
|
|
6948
|
+
throw new Error(`${path11}.query must be a non-empty string`);
|
|
6316
6949
|
}
|
|
6317
6950
|
return {
|
|
6318
6951
|
id,
|
|
6319
6952
|
query,
|
|
6320
|
-
queryType: parseQueryType(queryType, `${
|
|
6321
|
-
expected: parseExpected(expected, `${
|
|
6953
|
+
queryType: parseQueryType(queryType, `${path11}.queryType`),
|
|
6954
|
+
expected: parseExpected(expected, `${path11}.expected`)
|
|
6322
6955
|
};
|
|
6323
6956
|
}
|
|
6324
6957
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -6387,6 +7020,10 @@ function parseBudget(raw, sourceLabel) {
|
|
|
6387
7020
|
thresholds: {
|
|
6388
7021
|
hitAt5MaxDrop: thresholds.hitAt5MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.hitAt5MaxDrop, `${sourceLabel}.thresholds.hitAt5MaxDrop`),
|
|
6389
7022
|
mrrAt10MaxDrop: thresholds.mrrAt10MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.mrrAt10MaxDrop, `${sourceLabel}.thresholds.mrrAt10MaxDrop`),
|
|
7023
|
+
rawDistinctTop3RatioMaxDrop: thresholds.rawDistinctTop3RatioMaxDrop === void 0 ? void 0 : asPositiveNumber(
|
|
7024
|
+
thresholds.rawDistinctTop3RatioMaxDrop,
|
|
7025
|
+
`${sourceLabel}.thresholds.rawDistinctTop3RatioMaxDrop`
|
|
7026
|
+
),
|
|
6390
7027
|
p95LatencyMaxMultiplier: thresholds.p95LatencyMaxMultiplier === void 0 ? void 0 : asPositiveNumber(
|
|
6391
7028
|
thresholds.p95LatencyMaxMultiplier,
|
|
6392
7029
|
`${sourceLabel}.thresholds.p95LatencyMaxMultiplier`
|
|
@@ -6396,7 +7033,11 @@ function parseBudget(raw, sourceLabel) {
|
|
|
6396
7033
|
`${sourceLabel}.thresholds.p95LatencyMaxAbsoluteMs`
|
|
6397
7034
|
),
|
|
6398
7035
|
minHitAt5: thresholds.minHitAt5 === void 0 ? void 0 : asPositiveNumber(thresholds.minHitAt5, `${sourceLabel}.thresholds.minHitAt5`),
|
|
6399
|
-
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`)
|
|
7036
|
+
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`),
|
|
7037
|
+
minRawDistinctTop3Ratio: thresholds.minRawDistinctTop3Ratio === void 0 ? void 0 : asPositiveNumber(
|
|
7038
|
+
thresholds.minRawDistinctTop3Ratio,
|
|
7039
|
+
`${sourceLabel}.thresholds.minRawDistinctTop3Ratio`
|
|
7040
|
+
)
|
|
6400
7041
|
}
|
|
6401
7042
|
};
|
|
6402
7043
|
}
|
|
@@ -6890,8 +7531,12 @@ async function handleEvalCommand(args, cwd) {
|
|
|
6890
7531
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
6891
7532
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
6892
7533
|
}
|
|
6893
|
-
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath)
|
|
6894
|
-
|
|
7534
|
+
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath), {
|
|
7535
|
+
allowLegacyDiversityMetrics: true
|
|
7536
|
+
});
|
|
7537
|
+
const baselineSummary = loadSummary(path8.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
7538
|
+
allowLegacyDiversityMetrics: true
|
|
7539
|
+
});
|
|
6895
7540
|
const comparison = compareSummaries(
|
|
6896
7541
|
currentSummary,
|
|
6897
7542
|
baselineSummary,
|
|
@@ -6925,16 +7570,13 @@ function formatDefinitionLookup(results, query) {
|
|
|
6925
7570
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
6926
7571
|
}
|
|
6927
7572
|
const formatted = results.map((r, idx) => {
|
|
6928
|
-
const
|
|
6929
|
-
return `${
|
|
7573
|
+
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}`;
|
|
7574
|
+
return `${header} (score: ${r.score.toFixed(2)})
|
|
6930
7575
|
\`\`\`
|
|
6931
7576
|
${truncateContent(r.content)}
|
|
6932
7577
|
\`\`\``;
|
|
6933
7578
|
});
|
|
6934
|
-
|
|
6935
|
-
return `${header}
|
|
6936
|
-
|
|
6937
|
-
${formatted.join("\n\n")}`;
|
|
7579
|
+
return formatted.join("\n\n");
|
|
6938
7580
|
}
|
|
6939
7581
|
|
|
6940
7582
|
// src/mcp-server.ts
|
|
@@ -7388,7 +8030,10 @@ Use the implementation_lookup tool to find where this symbol is defined. This pr
|
|
|
7388
8030
|
return server;
|
|
7389
8031
|
}
|
|
7390
8032
|
|
|
7391
|
-
// src/
|
|
8033
|
+
// src/config/merger.ts
|
|
8034
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
8035
|
+
import * as path9 from "path";
|
|
8036
|
+
import * as os4 from "os";
|
|
7392
8037
|
function loadJsonFile(filePath) {
|
|
7393
8038
|
try {
|
|
7394
8039
|
if (existsSync6(filePath)) {
|
|
@@ -7399,25 +8044,101 @@ function loadJsonFile(filePath) {
|
|
|
7399
8044
|
}
|
|
7400
8045
|
return null;
|
|
7401
8046
|
}
|
|
7402
|
-
function
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
8047
|
+
function loadMergedConfig(projectRoot) {
|
|
8048
|
+
const globalConfigPath = path9.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8049
|
+
const globalConfig = loadJsonFile(globalConfigPath);
|
|
8050
|
+
const projectConfigPath = path9.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8051
|
+
const projectConfig = loadJsonFile(projectConfigPath);
|
|
8052
|
+
if (!globalConfig && !projectConfig) {
|
|
8053
|
+
return {};
|
|
8054
|
+
}
|
|
8055
|
+
if (!projectConfig && globalConfig) {
|
|
8056
|
+
return globalConfig;
|
|
8057
|
+
}
|
|
8058
|
+
if (!globalConfig && projectConfig) {
|
|
8059
|
+
return projectConfig;
|
|
8060
|
+
}
|
|
8061
|
+
const merged = { ...globalConfig };
|
|
8062
|
+
if (projectConfig && "embeddingProvider" in projectConfig) {
|
|
8063
|
+
merged.embeddingProvider = projectConfig.embeddingProvider;
|
|
8064
|
+
} else if (globalConfig && globalConfig.embeddingProvider) {
|
|
8065
|
+
merged.embeddingProvider = globalConfig.embeddingProvider;
|
|
8066
|
+
}
|
|
8067
|
+
if (projectConfig && "customProvider" in projectConfig) {
|
|
8068
|
+
merged.customProvider = projectConfig.customProvider;
|
|
8069
|
+
} else if (globalConfig && globalConfig.customProvider) {
|
|
8070
|
+
merged.customProvider = globalConfig.customProvider;
|
|
8071
|
+
}
|
|
8072
|
+
if (projectConfig && "embeddingModel" in projectConfig) {
|
|
8073
|
+
merged.embeddingModel = projectConfig.embeddingModel;
|
|
8074
|
+
} else if (globalConfig && globalConfig.embeddingModel) {
|
|
8075
|
+
merged.embeddingModel = globalConfig.embeddingModel;
|
|
8076
|
+
}
|
|
8077
|
+
if (projectConfig && "reranker" in projectConfig) {
|
|
8078
|
+
merged.reranker = projectConfig.reranker;
|
|
8079
|
+
} else if (globalConfig && globalConfig.reranker) {
|
|
8080
|
+
merged.reranker = globalConfig.reranker;
|
|
8081
|
+
}
|
|
8082
|
+
if (projectConfig && "include" in projectConfig) {
|
|
8083
|
+
merged.include = projectConfig.include;
|
|
8084
|
+
} else if (globalConfig && globalConfig.include) {
|
|
8085
|
+
merged.include = globalConfig.include;
|
|
8086
|
+
}
|
|
8087
|
+
if (projectConfig && "exclude" in projectConfig) {
|
|
8088
|
+
merged.exclude = projectConfig.exclude;
|
|
8089
|
+
} else if (globalConfig && globalConfig.exclude) {
|
|
8090
|
+
merged.exclude = globalConfig.exclude;
|
|
8091
|
+
}
|
|
8092
|
+
if (projectConfig && "indexing" in projectConfig) {
|
|
8093
|
+
merged.indexing = projectConfig.indexing;
|
|
8094
|
+
} else if (globalConfig && globalConfig.indexing) {
|
|
8095
|
+
merged.indexing = globalConfig.indexing;
|
|
8096
|
+
}
|
|
8097
|
+
if (projectConfig && "search" in projectConfig) {
|
|
8098
|
+
merged.search = projectConfig.search;
|
|
8099
|
+
} else if (globalConfig && globalConfig.search) {
|
|
8100
|
+
merged.search = globalConfig.search;
|
|
8101
|
+
}
|
|
8102
|
+
if (projectConfig && "debug" in projectConfig) {
|
|
8103
|
+
merged.debug = projectConfig.debug;
|
|
8104
|
+
} else if (globalConfig && globalConfig.debug) {
|
|
8105
|
+
merged.debug = globalConfig.debug;
|
|
8106
|
+
}
|
|
8107
|
+
if (projectConfig && "scope" in projectConfig) {
|
|
8108
|
+
merged.scope = projectConfig.scope;
|
|
8109
|
+
} else if (globalConfig && "scope" in globalConfig) {
|
|
8110
|
+
merged.scope = globalConfig.scope;
|
|
8111
|
+
}
|
|
8112
|
+
if (projectConfig) {
|
|
8113
|
+
for (const key of Object.keys(projectConfig)) {
|
|
8114
|
+
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") {
|
|
8115
|
+
continue;
|
|
8116
|
+
}
|
|
8117
|
+
merged[key] = projectConfig[key];
|
|
8118
|
+
}
|
|
7406
8119
|
}
|
|
7407
|
-
const
|
|
7408
|
-
|
|
7409
|
-
const
|
|
7410
|
-
|
|
7411
|
-
|
|
8120
|
+
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
|
|
8121
|
+
const projectKbs = projectConfig && Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
|
|
8122
|
+
const allKbs = [...globalKbs, ...projectKbs];
|
|
8123
|
+
const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
|
|
8124
|
+
merged.knowledgeBases = uniqueKbs;
|
|
8125
|
+
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
|
|
8126
|
+
const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
|
|
8127
|
+
const allAdditional = [...globalAdditional, ...projectAdditional];
|
|
8128
|
+
const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
|
|
8129
|
+
merged.additionalInclude = uniqueAdditional;
|
|
8130
|
+
return merged;
|
|
7412
8131
|
}
|
|
8132
|
+
|
|
8133
|
+
// src/cli.ts
|
|
7413
8134
|
function parseArgs(argv) {
|
|
7414
8135
|
let project = process.cwd();
|
|
7415
8136
|
let config;
|
|
7416
8137
|
for (let i = 2; i < argv.length; i++) {
|
|
7417
8138
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
7418
|
-
project =
|
|
8139
|
+
project = path10.resolve(argv[++i]);
|
|
7419
8140
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
7420
|
-
config =
|
|
8141
|
+
config = path10.resolve(argv[++i]);
|
|
7421
8142
|
}
|
|
7422
8143
|
}
|
|
7423
8144
|
return { project, config };
|
|
@@ -7428,7 +8149,7 @@ async function main() {
|
|
|
7428
8149
|
process.exit(exitCode);
|
|
7429
8150
|
}
|
|
7430
8151
|
const args = parseArgs(process.argv);
|
|
7431
|
-
const rawConfig =
|
|
8152
|
+
const rawConfig = loadMergedConfig(args.project);
|
|
7432
8153
|
const config = parseConfig(rawConfig);
|
|
7433
8154
|
const server = createMcpServer(args.project, config);
|
|
7434
8155
|
const transport = new StdioServerTransport();
|