opencode-codebase-index 0.8.1 → 0.10.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 +32 -13
- package/dist/cli.cjs +1526 -1293
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1525 -1292
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1235 -968
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1206 -939
- 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 +8 -2
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(path18, 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(path18);
|
|
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 = (path18, originalPath, doThrow) => {
|
|
521
|
+
if (!isString(path18)) {
|
|
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 (!path18) {
|
|
528
528
|
return doThrow(`path must not be empty`, TypeError);
|
|
529
529
|
}
|
|
530
|
-
if (checkPath.isNotRelative(
|
|
530
|
+
if (checkPath.isNotRelative(path18)) {
|
|
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 = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
|
|
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 path18 = originalPath && checkPath.convert(originalPath);
|
|
570
570
|
checkPath(
|
|
571
|
-
|
|
571
|
+
path18,
|
|
572
572
|
originalPath,
|
|
573
573
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
574
574
|
);
|
|
575
|
-
return this._t(
|
|
575
|
+
return this._t(path18, cache, checkUnignored, slices);
|
|
576
576
|
}
|
|
577
|
-
checkIgnore(
|
|
578
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
579
|
-
return this.test(
|
|
577
|
+
checkIgnore(path18) {
|
|
578
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
|
|
579
|
+
return this.test(path18);
|
|
580
580
|
}
|
|
581
|
-
const slices =
|
|
581
|
+
const slices = path18.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(path18, false, MODE_CHECK_IGNORE);
|
|
595
595
|
}
|
|
596
|
-
_t(
|
|
597
|
-
if (
|
|
598
|
-
return cache[
|
|
596
|
+
_t(path18, cache, checkUnignored, slices) {
|
|
597
|
+
if (path18 in cache) {
|
|
598
|
+
return cache[path18];
|
|
599
599
|
}
|
|
600
600
|
if (!slices) {
|
|
601
|
-
slices =
|
|
601
|
+
slices = path18.split(SLASH).filter(Boolean);
|
|
602
602
|
}
|
|
603
603
|
slices.pop();
|
|
604
604
|
if (!slices.length) {
|
|
605
|
-
return cache[
|
|
605
|
+
return cache[path18] = this._rules.test(path18, 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[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
|
|
614
614
|
}
|
|
615
|
-
ignores(
|
|
616
|
-
return this._test(
|
|
615
|
+
ignores(path18) {
|
|
616
|
+
return this._test(path18, this._ignoreCache, false).ignored;
|
|
617
617
|
}
|
|
618
618
|
createFilter() {
|
|
619
|
-
return (
|
|
619
|
+
return (path18) => !this.ignores(path18);
|
|
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(path18) {
|
|
626
|
+
return this._test(path18, this._testCache, true);
|
|
627
627
|
}
|
|
628
628
|
};
|
|
629
629
|
var factory = (options) => new Ignore2(options);
|
|
630
|
-
var isPathValid = (
|
|
630
|
+
var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, 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 = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
|
|
636
636
|
};
|
|
637
637
|
if (
|
|
638
638
|
// Detect `process` so that it can run in browsers.
|
|
@@ -649,7 +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
|
|
652
|
+
var path17 = __toESM(require("path"), 1);
|
|
653
653
|
|
|
654
654
|
// src/config/constants.ts
|
|
655
655
|
var DEFAULT_INCLUDE = [
|
|
@@ -665,7 +665,8 @@ var DEFAULT_INCLUDE = [
|
|
|
665
665
|
"**/*.{md,mdx}",
|
|
666
666
|
"**/*.{sh,bash,zsh}",
|
|
667
667
|
"**/*.{txt,html,htm}",
|
|
668
|
-
"**/*.zig"
|
|
668
|
+
"**/*.zig",
|
|
669
|
+
"**/*.gd"
|
|
669
670
|
];
|
|
670
671
|
var DEFAULT_EXCLUDE = [
|
|
671
672
|
"**/node_modules/**",
|
|
@@ -764,28 +765,7 @@ var AUTO_DETECT_PROVIDER_ORDER = [
|
|
|
764
765
|
"google"
|
|
765
766
|
];
|
|
766
767
|
|
|
767
|
-
// src/config/
|
|
768
|
-
var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
|
|
769
|
-
var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
|
|
770
|
-
function substituteEnvString(value, keyPath) {
|
|
771
|
-
const match = value.match(ENV_REFERENCE_PATTERN);
|
|
772
|
-
if (!match) {
|
|
773
|
-
if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
|
|
774
|
-
throw new Error(
|
|
775
|
-
`Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
return value;
|
|
779
|
-
}
|
|
780
|
-
const variableName = match[1];
|
|
781
|
-
const envValue = process.env[variableName];
|
|
782
|
-
if (envValue === void 0) {
|
|
783
|
-
throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
|
|
784
|
-
}
|
|
785
|
-
return envValue;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// src/config/schema.ts
|
|
768
|
+
// src/config/defaults.ts
|
|
789
769
|
function getDefaultIndexingConfig() {
|
|
790
770
|
return {
|
|
791
771
|
autoIndex: false,
|
|
@@ -814,15 +794,10 @@ function getDefaultSearchConfig() {
|
|
|
814
794
|
rrfK: 60,
|
|
815
795
|
rerankTopN: 20,
|
|
816
796
|
contextLines: 0,
|
|
817
|
-
routingHints: true
|
|
797
|
+
routingHints: true,
|
|
798
|
+
routingHintRole: "system"
|
|
818
799
|
};
|
|
819
800
|
}
|
|
820
|
-
function isValidFusionStrategy(value) {
|
|
821
|
-
return value === "weighted" || value === "rrf";
|
|
822
|
-
}
|
|
823
|
-
function isValidRerankerProvider(value) {
|
|
824
|
-
return value === "cohere" || value === "jina" || value === "custom";
|
|
825
|
-
}
|
|
826
801
|
function getDefaultRerankerBaseUrl(provider) {
|
|
827
802
|
switch (provider) {
|
|
828
803
|
case "cohere":
|
|
@@ -845,8 +820,37 @@ function getDefaultDebugConfig() {
|
|
|
845
820
|
metrics: true
|
|
846
821
|
};
|
|
847
822
|
}
|
|
823
|
+
|
|
824
|
+
// src/config/env-substitution.ts
|
|
825
|
+
var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
|
|
826
|
+
var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
|
|
827
|
+
function substituteEnvString(value, keyPath) {
|
|
828
|
+
const match = value.match(ENV_REFERENCE_PATTERN);
|
|
829
|
+
if (!match) {
|
|
830
|
+
if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return value;
|
|
836
|
+
}
|
|
837
|
+
const variableName = match[1];
|
|
838
|
+
const envValue = process.env[variableName];
|
|
839
|
+
if (envValue === void 0) {
|
|
840
|
+
throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
|
|
841
|
+
}
|
|
842
|
+
return envValue;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/config/validators.ts
|
|
848
846
|
var VALID_SCOPES = ["project", "global"];
|
|
849
847
|
var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
|
|
848
|
+
function isValidFusionStrategy(value) {
|
|
849
|
+
return value === "weighted" || value === "rrf";
|
|
850
|
+
}
|
|
851
|
+
function isValidRerankerProvider(value) {
|
|
852
|
+
return value === "cohere" || value === "jina" || value === "custom";
|
|
853
|
+
}
|
|
850
854
|
function isValidProvider(value) {
|
|
851
855
|
return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
|
|
852
856
|
}
|
|
@@ -874,6 +878,8 @@ function getResolvedStringArray(value, keyPath) {
|
|
|
874
878
|
function isValidLogLevel(value) {
|
|
875
879
|
return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
|
|
876
880
|
}
|
|
881
|
+
|
|
882
|
+
// src/config/schema.ts
|
|
877
883
|
function parseConfig(raw) {
|
|
878
884
|
const input = raw && typeof raw === "object" ? raw : {};
|
|
879
885
|
const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
|
|
@@ -910,7 +916,8 @@ function parseConfig(raw) {
|
|
|
910
916
|
rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
|
|
911
917
|
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
912
918
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
|
|
913
|
-
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
|
|
919
|
+
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
|
|
920
|
+
routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
|
|
914
921
|
};
|
|
915
922
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
916
923
|
const debug = {
|
|
@@ -928,9 +935,9 @@ function parseConfig(raw) {
|
|
|
928
935
|
const rawAdditionalInclude = input.additionalInclude;
|
|
929
936
|
const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
|
|
930
937
|
let embeddingProvider;
|
|
931
|
-
let embeddingModel
|
|
932
|
-
let customProvider
|
|
933
|
-
let reranker
|
|
938
|
+
let embeddingModel;
|
|
939
|
+
let customProvider;
|
|
940
|
+
let reranker;
|
|
934
941
|
if (embeddingProviderValue === "custom") {
|
|
935
942
|
embeddingProvider = "custom";
|
|
936
943
|
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
@@ -963,7 +970,7 @@ function parseConfig(raw) {
|
|
|
963
970
|
embeddingProvider = embeddingProviderValue;
|
|
964
971
|
const rawEmbeddingModel = input.embeddingModel;
|
|
965
972
|
if (typeof rawEmbeddingModel === "string") {
|
|
966
|
-
const embeddingModelValue =
|
|
973
|
+
const embeddingModelValue = getResolvedString(rawEmbeddingModel, "$root.embeddingModel");
|
|
967
974
|
if (embeddingModelValue) {
|
|
968
975
|
embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
969
976
|
}
|
|
@@ -1025,9 +1032,6 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
|
|
|
1025
1032
|
(provider) => provider in EMBEDDING_MODELS
|
|
1026
1033
|
);
|
|
1027
1034
|
|
|
1028
|
-
// src/eval/cli.ts
|
|
1029
|
-
var path10 = __toESM(require("path"), 1);
|
|
1030
|
-
|
|
1031
1035
|
// src/eval/compare.ts
|
|
1032
1036
|
function metricDelta(current, baseline) {
|
|
1033
1037
|
const absolute = current - baseline;
|
|
@@ -1069,9 +1073,11 @@ function compareSummaries(current, baseline, againstPath) {
|
|
|
1069
1073
|
// src/eval/reports.ts
|
|
1070
1074
|
var import_fs = require("fs");
|
|
1071
1075
|
var path = __toESM(require("path"), 1);
|
|
1072
|
-
|
|
1076
|
+
|
|
1077
|
+
// src/eval/report-formatters.ts
|
|
1078
|
+
function assertFiniteNumber(value, path18) {
|
|
1073
1079
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1074
|
-
throw new Error(`${
|
|
1080
|
+
throw new Error(`${path18} must be a finite number`);
|
|
1075
1081
|
}
|
|
1076
1082
|
return value;
|
|
1077
1083
|
}
|
|
@@ -1111,9 +1117,23 @@ function signed(value, digits = 4) {
|
|
|
1111
1117
|
const formatted = value.toFixed(digits);
|
|
1112
1118
|
return value > 0 ? `+${formatted}` : formatted;
|
|
1113
1119
|
}
|
|
1120
|
+
|
|
1121
|
+
// src/eval/reports.ts
|
|
1114
1122
|
function loadSummary(summaryPath, options) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1123
|
+
try {
|
|
1124
|
+
const raw = (0, import_fs.readFileSync)(summaryPath, "utf-8");
|
|
1125
|
+
const parsed = JSON.parse(raw);
|
|
1126
|
+
return validateSummary(parsed, summaryPath, options);
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
if (error instanceof SyntaxError) {
|
|
1129
|
+
const message = error.message;
|
|
1130
|
+
throw new Error(`Failed to parse eval summary JSON at ${summaryPath}: ${message}`);
|
|
1131
|
+
}
|
|
1132
|
+
if (error instanceof Error) {
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
throw new Error(`Failed to load eval summary at ${summaryPath}: ${String(error)}`);
|
|
1136
|
+
}
|
|
1117
1137
|
}
|
|
1118
1138
|
function createRunDirectory(outputRoot, timestampOverride) {
|
|
1119
1139
|
const timestamp = (timestampOverride ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
|
|
@@ -1245,445 +1265,106 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1245
1265
|
}
|
|
1246
1266
|
|
|
1247
1267
|
// src/eval/runner.ts
|
|
1248
|
-
var import_fs9 = require("fs");
|
|
1249
1268
|
var import_fs10 = require("fs");
|
|
1250
|
-
var
|
|
1251
|
-
var import_fs12 = require("fs");
|
|
1252
|
-
var import_fs13 = require("fs");
|
|
1253
|
-
var os5 = __toESM(require("os"), 1);
|
|
1254
|
-
var path9 = __toESM(require("path"), 1);
|
|
1269
|
+
var path12 = __toESM(require("path"), 1);
|
|
1255
1270
|
var import_perf_hooks2 = require("perf_hooks");
|
|
1256
1271
|
|
|
1257
|
-
// src/
|
|
1258
|
-
var
|
|
1259
|
-
var
|
|
1260
|
-
var
|
|
1272
|
+
// src/indexer/index.ts
|
|
1273
|
+
var import_fs7 = require("fs");
|
|
1274
|
+
var path9 = __toESM(require("path"), 1);
|
|
1275
|
+
var import_perf_hooks = require("perf_hooks");
|
|
1261
1276
|
|
|
1262
|
-
//
|
|
1263
|
-
var
|
|
1264
|
-
var os = __toESM(require("os"), 1);
|
|
1265
|
-
var path3 = __toESM(require("path"), 1);
|
|
1277
|
+
// node_modules/eventemitter3/index.mjs
|
|
1278
|
+
var import_index = __toESM(require_eventemitter3(), 1);
|
|
1266
1279
|
|
|
1267
|
-
//
|
|
1268
|
-
var
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
return [];
|
|
1274
|
-
}
|
|
1275
|
-
try {
|
|
1276
|
-
return (0, import_fs2.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
1277
|
-
} catch {
|
|
1278
|
-
return [];
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
function resolveCommonGitDir(gitDir) {
|
|
1282
|
-
const commonDirPath = path2.join(gitDir, "commondir");
|
|
1283
|
-
if (!(0, import_fs2.existsSync)(commonDirPath)) {
|
|
1284
|
-
return gitDir;
|
|
1280
|
+
// node_modules/p-timeout/index.js
|
|
1281
|
+
var TimeoutError = class _TimeoutError extends Error {
|
|
1282
|
+
name = "TimeoutError";
|
|
1283
|
+
constructor(message, options) {
|
|
1284
|
+
super(message, options);
|
|
1285
|
+
Error.captureStackTrace?.(this, _TimeoutError);
|
|
1285
1286
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1287
|
+
};
|
|
1288
|
+
var getAbortedReason = (signal) => signal.reason ?? new DOMException("This operation was aborted.", "AbortError");
|
|
1289
|
+
function pTimeout(promise, options) {
|
|
1290
|
+
const {
|
|
1291
|
+
milliseconds,
|
|
1292
|
+
fallback,
|
|
1293
|
+
message,
|
|
1294
|
+
customTimers = { setTimeout, clearTimeout },
|
|
1295
|
+
signal
|
|
1296
|
+
} = options;
|
|
1297
|
+
let timer;
|
|
1298
|
+
let abortHandler;
|
|
1299
|
+
const wrappedPromise = new Promise((resolve10, reject) => {
|
|
1300
|
+
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
1301
|
+
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
1290
1302
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
return
|
|
1303
|
+
if (signal?.aborted) {
|
|
1304
|
+
reject(getAbortedReason(signal));
|
|
1305
|
+
return;
|
|
1294
1306
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
function resolveWorktreeMainRepoRoot(repoRoot) {
|
|
1301
|
-
const gitDir = resolveGitDir(repoRoot);
|
|
1302
|
-
if (!gitDir) {
|
|
1303
|
-
return null;
|
|
1304
|
-
}
|
|
1305
|
-
const commonGitDir = resolveCommonGitDir(gitDir);
|
|
1306
|
-
if (commonGitDir === gitDir || path2.basename(commonGitDir) !== ".git") {
|
|
1307
|
-
return null;
|
|
1308
|
-
}
|
|
1309
|
-
const mainRepoRoot = path2.dirname(commonGitDir);
|
|
1310
|
-
if (!(0, import_fs2.existsSync)(mainRepoRoot)) {
|
|
1311
|
-
return null;
|
|
1312
|
-
}
|
|
1313
|
-
return path2.resolve(mainRepoRoot) === path2.resolve(repoRoot) ? null : mainRepoRoot;
|
|
1314
|
-
}
|
|
1315
|
-
function resolveGitDir(repoRoot) {
|
|
1316
|
-
const gitPath = path2.join(repoRoot, ".git");
|
|
1317
|
-
if (!(0, import_fs2.existsSync)(gitPath)) {
|
|
1318
|
-
return null;
|
|
1319
|
-
}
|
|
1320
|
-
try {
|
|
1321
|
-
const stat = (0, import_fs2.statSync)(gitPath);
|
|
1322
|
-
if (stat.isDirectory()) {
|
|
1323
|
-
return gitPath;
|
|
1307
|
+
if (signal) {
|
|
1308
|
+
abortHandler = () => {
|
|
1309
|
+
reject(getAbortedReason(signal));
|
|
1310
|
+
};
|
|
1311
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1324
1312
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1313
|
+
promise.then(resolve10, reject);
|
|
1314
|
+
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const timeoutError = new TimeoutError();
|
|
1318
|
+
timer = customTimers.setTimeout.call(void 0, () => {
|
|
1319
|
+
if (fallback) {
|
|
1320
|
+
try {
|
|
1321
|
+
resolve10(fallback());
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
reject(error);
|
|
1333
1324
|
}
|
|
1325
|
+
return;
|
|
1334
1326
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
}
|
|
1338
|
-
return null;
|
|
1339
|
-
}
|
|
1340
|
-
function isGitRepo(dir) {
|
|
1341
|
-
return resolveGitDir(dir) !== null;
|
|
1342
|
-
}
|
|
1343
|
-
function getCurrentBranch(repoRoot) {
|
|
1344
|
-
const gitDir = resolveGitDir(repoRoot);
|
|
1345
|
-
if (!gitDir) {
|
|
1346
|
-
return null;
|
|
1347
|
-
}
|
|
1348
|
-
const headPath = path2.join(gitDir, "HEAD");
|
|
1349
|
-
if (!(0, import_fs2.existsSync)(headPath)) {
|
|
1350
|
-
return null;
|
|
1351
|
-
}
|
|
1352
|
-
try {
|
|
1353
|
-
const headContent = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
|
|
1354
|
-
const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
|
|
1355
|
-
if (match) {
|
|
1356
|
-
return match[1];
|
|
1357
|
-
}
|
|
1358
|
-
if (/^[0-9a-f]{40}$/i.test(headContent)) {
|
|
1359
|
-
return headContent.slice(0, 7);
|
|
1360
|
-
}
|
|
1361
|
-
return null;
|
|
1362
|
-
} catch {
|
|
1363
|
-
return null;
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
function getBaseBranch(repoRoot) {
|
|
1367
|
-
const gitDir = resolveGitDir(repoRoot);
|
|
1368
|
-
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
1369
|
-
const candidates = ["main", "master", "develop", "trunk"];
|
|
1370
|
-
if (refStoreDir) {
|
|
1371
|
-
for (const candidate of candidates) {
|
|
1372
|
-
const refPath = path2.join(refStoreDir, "refs", "heads", candidate);
|
|
1373
|
-
if ((0, import_fs2.existsSync)(refPath)) {
|
|
1374
|
-
return candidate;
|
|
1327
|
+
if (typeof promise.cancel === "function") {
|
|
1328
|
+
promise.cancel();
|
|
1375
1329
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1330
|
+
if (message === false) {
|
|
1331
|
+
resolve10();
|
|
1332
|
+
} else if (message instanceof Error) {
|
|
1333
|
+
reject(message);
|
|
1334
|
+
} else {
|
|
1335
|
+
timeoutError.message = message ?? `Promise timed out after ${milliseconds} milliseconds`;
|
|
1336
|
+
reject(timeoutError);
|
|
1379
1337
|
}
|
|
1338
|
+
}, milliseconds);
|
|
1339
|
+
});
|
|
1340
|
+
const cancelablePromise = wrappedPromise.finally(() => {
|
|
1341
|
+
cancelablePromise.clear();
|
|
1342
|
+
if (abortHandler && signal) {
|
|
1343
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1380
1344
|
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
return getCurrentBranch(repoRoot) ?? "default";
|
|
1345
|
+
});
|
|
1346
|
+
cancelablePromise.clear = () => {
|
|
1347
|
+
customTimers.clearTimeout.call(void 0, timer);
|
|
1348
|
+
timer = void 0;
|
|
1349
|
+
};
|
|
1350
|
+
return cancelablePromise;
|
|
1389
1351
|
}
|
|
1390
1352
|
|
|
1391
|
-
//
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1353
|
+
// node_modules/p-queue/dist/lower-bound.js
|
|
1354
|
+
function lowerBound(array, value, comparator) {
|
|
1355
|
+
let first = 0;
|
|
1356
|
+
let count = array.length;
|
|
1357
|
+
while (count > 0) {
|
|
1358
|
+
const step = Math.trunc(count / 2);
|
|
1359
|
+
let it = first + step;
|
|
1360
|
+
if (comparator(array[it], value) <= 0) {
|
|
1361
|
+
first = ++it;
|
|
1362
|
+
count -= step + 1;
|
|
1363
|
+
} else {
|
|
1364
|
+
count = step;
|
|
1365
|
+
}
|
|
1398
1366
|
}
|
|
1399
|
-
|
|
1400
|
-
return (0, import_fs3.existsSync)(fallbackPath) ? fallbackPath : null;
|
|
1401
|
-
}
|
|
1402
|
-
function hasProjectConfig(projectRoot) {
|
|
1403
|
-
return (0, import_fs3.existsSync)(path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
|
|
1404
|
-
}
|
|
1405
|
-
function getGlobalIndexPath() {
|
|
1406
|
-
return path3.join(os.homedir(), ".opencode", "global-index");
|
|
1407
|
-
}
|
|
1408
|
-
function resolveProjectConfigPath(projectRoot) {
|
|
1409
|
-
const localConfigPath = path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
|
|
1410
|
-
if ((0, import_fs3.existsSync)(localConfigPath)) {
|
|
1411
|
-
return localConfigPath;
|
|
1412
|
-
}
|
|
1413
|
-
return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
|
|
1414
|
-
}
|
|
1415
|
-
function resolveProjectIndexPath(projectRoot, scope) {
|
|
1416
|
-
if (scope === "global") {
|
|
1417
|
-
return getGlobalIndexPath();
|
|
1418
|
-
}
|
|
1419
|
-
const localIndexPath = path3.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
|
|
1420
|
-
if ((0, import_fs3.existsSync)(localIndexPath)) {
|
|
1421
|
-
return localIndexPath;
|
|
1422
|
-
}
|
|
1423
|
-
if (hasProjectConfig(projectRoot)) {
|
|
1424
|
-
return localIndexPath;
|
|
1425
|
-
}
|
|
1426
|
-
return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// src/config/merger.ts
|
|
1430
|
-
function loadJsonFile(filePath) {
|
|
1431
|
-
try {
|
|
1432
|
-
if ((0, import_fs4.existsSync)(filePath)) {
|
|
1433
|
-
const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
|
|
1434
|
-
return JSON.parse(content);
|
|
1435
|
-
}
|
|
1436
|
-
} catch {
|
|
1437
|
-
}
|
|
1438
|
-
return null;
|
|
1439
|
-
}
|
|
1440
|
-
function normalizeRelativeConfigPath(candidate) {
|
|
1441
|
-
return candidate.replace(/\\/g, "/");
|
|
1442
|
-
}
|
|
1443
|
-
function rebasePathEntries(values, fromDir, toDir) {
|
|
1444
|
-
if (!Array.isArray(values)) {
|
|
1445
|
-
return [];
|
|
1446
|
-
}
|
|
1447
|
-
return values.filter((value) => typeof value === "string").map((value) => {
|
|
1448
|
-
const trimmed = value.trim();
|
|
1449
|
-
if (!trimmed || path4.isAbsolute(trimmed)) {
|
|
1450
|
-
return trimmed;
|
|
1451
|
-
}
|
|
1452
|
-
return normalizeRelativeConfigPath(path4.normalize(path4.relative(toDir, path4.resolve(fromDir, trimmed))));
|
|
1453
|
-
}).filter(Boolean);
|
|
1454
|
-
}
|
|
1455
|
-
function isWithinRoot(rootDir, targetPath) {
|
|
1456
|
-
const relativePath = path4.relative(rootDir, targetPath);
|
|
1457
|
-
return relativePath === "" || !relativePath.startsWith("..") && !path4.isAbsolute(relativePath);
|
|
1458
|
-
}
|
|
1459
|
-
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
1460
|
-
if (!Array.isArray(values)) {
|
|
1461
|
-
return [];
|
|
1462
|
-
}
|
|
1463
|
-
return values.filter((value) => typeof value === "string").map((value) => {
|
|
1464
|
-
const trimmed = value.trim();
|
|
1465
|
-
if (!trimmed) {
|
|
1466
|
-
return trimmed;
|
|
1467
|
-
}
|
|
1468
|
-
if (path4.isAbsolute(trimmed)) {
|
|
1469
|
-
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
1470
|
-
return normalizeRelativeConfigPath(path4.normalize(path4.relative(sourceRoot, trimmed) || "."));
|
|
1471
|
-
}
|
|
1472
|
-
return path4.normalize(trimmed);
|
|
1473
|
-
}
|
|
1474
|
-
const resolvedFromSource = path4.resolve(sourceRoot, trimmed);
|
|
1475
|
-
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
1476
|
-
return normalizeRelativeConfigPath(path4.normalize(trimmed));
|
|
1477
|
-
}
|
|
1478
|
-
return normalizeRelativeConfigPath(path4.normalize(path4.relative(targetRoot, resolvedFromSource)));
|
|
1479
|
-
}).filter(Boolean);
|
|
1480
|
-
}
|
|
1481
|
-
function materializeLocalProjectConfig(projectRoot, config) {
|
|
1482
|
-
const localConfigPath = path4.join(projectRoot, ".opencode", "codebase-index.json");
|
|
1483
|
-
(0, import_fs4.mkdirSync)(path4.dirname(localConfigPath), { recursive: true });
|
|
1484
|
-
(0, import_fs4.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1485
|
-
return localConfigPath;
|
|
1486
|
-
}
|
|
1487
|
-
function loadProjectConfigLayer(projectRoot) {
|
|
1488
|
-
const projectConfigPath = resolveProjectConfigPath(projectRoot);
|
|
1489
|
-
const projectConfig = loadJsonFile(projectConfigPath);
|
|
1490
|
-
if (!projectConfig) {
|
|
1491
|
-
return {};
|
|
1492
|
-
}
|
|
1493
|
-
const normalizedConfig = { ...projectConfig };
|
|
1494
|
-
const projectConfigBaseDir = path4.dirname(path4.dirname(projectConfigPath));
|
|
1495
|
-
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
1496
|
-
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
1497
|
-
normalizedConfig.knowledgeBases,
|
|
1498
|
-
projectConfigBaseDir,
|
|
1499
|
-
projectRoot
|
|
1500
|
-
);
|
|
1501
|
-
}
|
|
1502
|
-
return normalizedConfig;
|
|
1503
|
-
}
|
|
1504
|
-
function loadMergedConfig(projectRoot) {
|
|
1505
|
-
const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
|
|
1506
|
-
const globalConfig = loadJsonFile(globalConfigPath);
|
|
1507
|
-
const projectConfigPath = resolveProjectConfigPath(projectRoot);
|
|
1508
|
-
const projectConfig = loadJsonFile(projectConfigPath);
|
|
1509
|
-
const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
|
|
1510
|
-
if (!globalConfig && !projectConfig) {
|
|
1511
|
-
return {};
|
|
1512
|
-
}
|
|
1513
|
-
if (!projectConfig && globalConfig) {
|
|
1514
|
-
return globalConfig;
|
|
1515
|
-
}
|
|
1516
|
-
if (!globalConfig && projectConfig) {
|
|
1517
|
-
return normalizedProjectConfig;
|
|
1518
|
-
}
|
|
1519
|
-
const merged = { ...globalConfig };
|
|
1520
|
-
if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
|
|
1521
|
-
merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
|
|
1522
|
-
} else if (globalConfig && globalConfig.embeddingProvider) {
|
|
1523
|
-
merged.embeddingProvider = globalConfig.embeddingProvider;
|
|
1524
|
-
}
|
|
1525
|
-
if (projectConfig && "customProvider" in normalizedProjectConfig) {
|
|
1526
|
-
merged.customProvider = normalizedProjectConfig.customProvider;
|
|
1527
|
-
} else if (globalConfig && globalConfig.customProvider) {
|
|
1528
|
-
merged.customProvider = globalConfig.customProvider;
|
|
1529
|
-
}
|
|
1530
|
-
if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
|
|
1531
|
-
merged.embeddingModel = normalizedProjectConfig.embeddingModel;
|
|
1532
|
-
} else if (globalConfig && globalConfig.embeddingModel) {
|
|
1533
|
-
merged.embeddingModel = globalConfig.embeddingModel;
|
|
1534
|
-
}
|
|
1535
|
-
if (projectConfig && "reranker" in normalizedProjectConfig) {
|
|
1536
|
-
merged.reranker = normalizedProjectConfig.reranker;
|
|
1537
|
-
} else if (globalConfig && globalConfig.reranker) {
|
|
1538
|
-
merged.reranker = globalConfig.reranker;
|
|
1539
|
-
}
|
|
1540
|
-
if (projectConfig && "include" in normalizedProjectConfig) {
|
|
1541
|
-
merged.include = normalizedProjectConfig.include;
|
|
1542
|
-
} else if (globalConfig && globalConfig.include) {
|
|
1543
|
-
merged.include = globalConfig.include;
|
|
1544
|
-
}
|
|
1545
|
-
if (projectConfig && "exclude" in normalizedProjectConfig) {
|
|
1546
|
-
merged.exclude = normalizedProjectConfig.exclude;
|
|
1547
|
-
} else if (globalConfig && globalConfig.exclude) {
|
|
1548
|
-
merged.exclude = globalConfig.exclude;
|
|
1549
|
-
}
|
|
1550
|
-
if (projectConfig && "indexing" in normalizedProjectConfig) {
|
|
1551
|
-
merged.indexing = normalizedProjectConfig.indexing;
|
|
1552
|
-
} else if (globalConfig && globalConfig.indexing) {
|
|
1553
|
-
merged.indexing = globalConfig.indexing;
|
|
1554
|
-
}
|
|
1555
|
-
if (projectConfig && "search" in normalizedProjectConfig) {
|
|
1556
|
-
merged.search = normalizedProjectConfig.search;
|
|
1557
|
-
} else if (globalConfig && globalConfig.search) {
|
|
1558
|
-
merged.search = globalConfig.search;
|
|
1559
|
-
}
|
|
1560
|
-
if (projectConfig && "debug" in normalizedProjectConfig) {
|
|
1561
|
-
merged.debug = normalizedProjectConfig.debug;
|
|
1562
|
-
} else if (globalConfig && globalConfig.debug) {
|
|
1563
|
-
merged.debug = globalConfig.debug;
|
|
1564
|
-
}
|
|
1565
|
-
if (projectConfig && "scope" in normalizedProjectConfig) {
|
|
1566
|
-
merged.scope = normalizedProjectConfig.scope;
|
|
1567
|
-
} else if (globalConfig && "scope" in globalConfig) {
|
|
1568
|
-
merged.scope = globalConfig.scope;
|
|
1569
|
-
}
|
|
1570
|
-
if (projectConfig) {
|
|
1571
|
-
for (const key of Object.keys(projectConfig)) {
|
|
1572
|
-
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") {
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
merged[key] = normalizedProjectConfig[key];
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
|
|
1579
|
-
const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
|
|
1580
|
-
const allKbs = [...globalKbs, ...projectKbs];
|
|
1581
|
-
const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
|
|
1582
|
-
merged.knowledgeBases = uniqueKbs;
|
|
1583
|
-
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
|
|
1584
|
-
const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
|
|
1585
|
-
const allAdditional = [...globalAdditional, ...projectAdditional];
|
|
1586
|
-
const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
|
|
1587
|
-
merged.additionalInclude = uniqueAdditional;
|
|
1588
|
-
return merged;
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
// src/indexer/index.ts
|
|
1592
|
-
var import_fs7 = require("fs");
|
|
1593
|
-
var path8 = __toESM(require("path"), 1);
|
|
1594
|
-
var import_perf_hooks = require("perf_hooks");
|
|
1595
|
-
|
|
1596
|
-
// node_modules/eventemitter3/index.mjs
|
|
1597
|
-
var import_index = __toESM(require_eventemitter3(), 1);
|
|
1598
|
-
|
|
1599
|
-
// node_modules/p-timeout/index.js
|
|
1600
|
-
var TimeoutError = class _TimeoutError extends Error {
|
|
1601
|
-
name = "TimeoutError";
|
|
1602
|
-
constructor(message, options) {
|
|
1603
|
-
super(message, options);
|
|
1604
|
-
Error.captureStackTrace?.(this, _TimeoutError);
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1607
|
-
var getAbortedReason = (signal) => signal.reason ?? new DOMException("This operation was aborted.", "AbortError");
|
|
1608
|
-
function pTimeout(promise, options) {
|
|
1609
|
-
const {
|
|
1610
|
-
milliseconds,
|
|
1611
|
-
fallback,
|
|
1612
|
-
message,
|
|
1613
|
-
customTimers = { setTimeout, clearTimeout },
|
|
1614
|
-
signal
|
|
1615
|
-
} = options;
|
|
1616
|
-
let timer;
|
|
1617
|
-
let abortHandler;
|
|
1618
|
-
const wrappedPromise = new Promise((resolve8, reject) => {
|
|
1619
|
-
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
1620
|
-
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
1621
|
-
}
|
|
1622
|
-
if (signal?.aborted) {
|
|
1623
|
-
reject(getAbortedReason(signal));
|
|
1624
|
-
return;
|
|
1625
|
-
}
|
|
1626
|
-
if (signal) {
|
|
1627
|
-
abortHandler = () => {
|
|
1628
|
-
reject(getAbortedReason(signal));
|
|
1629
|
-
};
|
|
1630
|
-
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1631
|
-
}
|
|
1632
|
-
promise.then(resolve8, reject);
|
|
1633
|
-
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
const timeoutError = new TimeoutError();
|
|
1637
|
-
timer = customTimers.setTimeout.call(void 0, () => {
|
|
1638
|
-
if (fallback) {
|
|
1639
|
-
try {
|
|
1640
|
-
resolve8(fallback());
|
|
1641
|
-
} catch (error) {
|
|
1642
|
-
reject(error);
|
|
1643
|
-
}
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
if (typeof promise.cancel === "function") {
|
|
1647
|
-
promise.cancel();
|
|
1648
|
-
}
|
|
1649
|
-
if (message === false) {
|
|
1650
|
-
resolve8();
|
|
1651
|
-
} else if (message instanceof Error) {
|
|
1652
|
-
reject(message);
|
|
1653
|
-
} else {
|
|
1654
|
-
timeoutError.message = message ?? `Promise timed out after ${milliseconds} milliseconds`;
|
|
1655
|
-
reject(timeoutError);
|
|
1656
|
-
}
|
|
1657
|
-
}, milliseconds);
|
|
1658
|
-
});
|
|
1659
|
-
const cancelablePromise = wrappedPromise.finally(() => {
|
|
1660
|
-
cancelablePromise.clear();
|
|
1661
|
-
if (abortHandler && signal) {
|
|
1662
|
-
signal.removeEventListener("abort", abortHandler);
|
|
1663
|
-
}
|
|
1664
|
-
});
|
|
1665
|
-
cancelablePromise.clear = () => {
|
|
1666
|
-
customTimers.clearTimeout.call(void 0, timer);
|
|
1667
|
-
timer = void 0;
|
|
1668
|
-
};
|
|
1669
|
-
return cancelablePromise;
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
// node_modules/p-queue/dist/lower-bound.js
|
|
1673
|
-
function lowerBound(array, value, comparator) {
|
|
1674
|
-
let first = 0;
|
|
1675
|
-
let count = array.length;
|
|
1676
|
-
while (count > 0) {
|
|
1677
|
-
const step = Math.trunc(count / 2);
|
|
1678
|
-
let it = first + step;
|
|
1679
|
-
if (comparator(array[it], value) <= 0) {
|
|
1680
|
-
first = ++it;
|
|
1681
|
-
count -= step + 1;
|
|
1682
|
-
} else {
|
|
1683
|
-
count = step;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
return first;
|
|
1367
|
+
return first;
|
|
1687
1368
|
}
|
|
1688
1369
|
|
|
1689
1370
|
// node_modules/p-queue/dist/priority-queue.js
|
|
@@ -2049,7 +1730,7 @@ var PQueue = class extends import_index.default {
|
|
|
2049
1730
|
// Assign unique ID if not provided
|
|
2050
1731
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
2051
1732
|
};
|
|
2052
|
-
return new Promise((
|
|
1733
|
+
return new Promise((resolve10, reject) => {
|
|
2053
1734
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
2054
1735
|
let cleanupQueueAbortHandler = () => void 0;
|
|
2055
1736
|
const run = async () => {
|
|
@@ -2089,7 +1770,7 @@ var PQueue = class extends import_index.default {
|
|
|
2089
1770
|
})]);
|
|
2090
1771
|
}
|
|
2091
1772
|
const result = await operation;
|
|
2092
|
-
|
|
1773
|
+
resolve10(result);
|
|
2093
1774
|
this.emit("completed", result);
|
|
2094
1775
|
} catch (error) {
|
|
2095
1776
|
reject(error);
|
|
@@ -2277,13 +1958,13 @@ var PQueue = class extends import_index.default {
|
|
|
2277
1958
|
});
|
|
2278
1959
|
}
|
|
2279
1960
|
async #onEvent(event, filter) {
|
|
2280
|
-
return new Promise((
|
|
1961
|
+
return new Promise((resolve10) => {
|
|
2281
1962
|
const listener = () => {
|
|
2282
1963
|
if (filter && !filter()) {
|
|
2283
1964
|
return;
|
|
2284
1965
|
}
|
|
2285
1966
|
this.off(event, listener);
|
|
2286
|
-
|
|
1967
|
+
resolve10();
|
|
2287
1968
|
};
|
|
2288
1969
|
this.on(event, listener);
|
|
2289
1970
|
});
|
|
@@ -2569,7 +2250,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2569
2250
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
2570
2251
|
options.signal?.throwIfAborted();
|
|
2571
2252
|
if (finalDelay > 0) {
|
|
2572
|
-
await new Promise((
|
|
2253
|
+
await new Promise((resolve10, reject) => {
|
|
2573
2254
|
const onAbort = () => {
|
|
2574
2255
|
clearTimeout(timeoutToken);
|
|
2575
2256
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -2577,7 +2258,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2577
2258
|
};
|
|
2578
2259
|
const timeoutToken = setTimeout(() => {
|
|
2579
2260
|
options.signal?.removeEventListener("abort", onAbort);
|
|
2580
|
-
|
|
2261
|
+
resolve10();
|
|
2581
2262
|
}, finalDelay);
|
|
2582
2263
|
if (options.unref) {
|
|
2583
2264
|
timeoutToken.unref?.();
|
|
@@ -2638,17 +2319,17 @@ async function pRetry(input, options = {}) {
|
|
|
2638
2319
|
}
|
|
2639
2320
|
|
|
2640
2321
|
// src/embeddings/detector.ts
|
|
2641
|
-
var
|
|
2642
|
-
var
|
|
2643
|
-
var
|
|
2322
|
+
var import_fs2 = require("fs");
|
|
2323
|
+
var path2 = __toESM(require("path"), 1);
|
|
2324
|
+
var os = __toESM(require("os"), 1);
|
|
2644
2325
|
function getOpenCodeAuthPath() {
|
|
2645
|
-
return
|
|
2326
|
+
return path2.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
2646
2327
|
}
|
|
2647
2328
|
function loadOpenCodeAuth() {
|
|
2648
2329
|
const authPath = getOpenCodeAuthPath();
|
|
2649
2330
|
try {
|
|
2650
|
-
if ((0,
|
|
2651
|
-
return JSON.parse((0,
|
|
2331
|
+
if ((0, import_fs2.existsSync)(authPath)) {
|
|
2332
|
+
return JSON.parse((0, import_fs2.readFileSync)(authPath, "utf-8"));
|
|
2652
2333
|
}
|
|
2653
2334
|
} catch {
|
|
2654
2335
|
}
|
|
@@ -2715,7 +2396,8 @@ function getGitHubCopilotCredentials() {
|
|
|
2715
2396
|
if (!copilotAuth || copilotAuth.type !== "oauth") {
|
|
2716
2397
|
return null;
|
|
2717
2398
|
}
|
|
2718
|
-
const
|
|
2399
|
+
const auth = copilotAuth;
|
|
2400
|
+
const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
|
|
2719
2401
|
return {
|
|
2720
2402
|
provider: "github-copilot",
|
|
2721
2403
|
baseUrl,
|
|
@@ -2811,42 +2493,12 @@ function createCustomProviderInfo(config) {
|
|
|
2811
2493
|
};
|
|
2812
2494
|
}
|
|
2813
2495
|
|
|
2814
|
-
// src/embeddings/provider.ts
|
|
2815
|
-
var
|
|
2816
|
-
constructor(message) {
|
|
2817
|
-
super(message);
|
|
2818
|
-
this.name = "CustomProviderNonRetryableError";
|
|
2819
|
-
}
|
|
2820
|
-
};
|
|
2821
|
-
function createEmbeddingProvider(configuredProviderInfo) {
|
|
2822
|
-
switch (configuredProviderInfo.provider) {
|
|
2823
|
-
case "github-copilot":
|
|
2824
|
-
return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2825
|
-
case "openai":
|
|
2826
|
-
return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2827
|
-
case "google":
|
|
2828
|
-
return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2829
|
-
case "ollama":
|
|
2830
|
-
return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2831
|
-
case "custom":
|
|
2832
|
-
return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2833
|
-
default: {
|
|
2834
|
-
const _exhaustive = configuredProviderInfo;
|
|
2835
|
-
throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
var GitHubCopilotEmbeddingProvider = class {
|
|
2496
|
+
// src/embeddings/provider-types.ts
|
|
2497
|
+
var BaseEmbeddingProvider = class {
|
|
2840
2498
|
constructor(credentials, modelInfo) {
|
|
2841
2499
|
this.credentials = credentials;
|
|
2842
2500
|
this.modelInfo = modelInfo;
|
|
2843
2501
|
}
|
|
2844
|
-
getToken() {
|
|
2845
|
-
if (!this.credentials.refreshToken) {
|
|
2846
|
-
throw new Error("No OAuth token available for GitHub");
|
|
2847
|
-
}
|
|
2848
|
-
return this.credentials.refreshToken;
|
|
2849
|
-
}
|
|
2850
2502
|
async embedQuery(query) {
|
|
2851
2503
|
const result = await this.embedBatch([query]);
|
|
2852
2504
|
return {
|
|
@@ -2861,69 +2513,204 @@ var GitHubCopilotEmbeddingProvider = class {
|
|
|
2861
2513
|
tokensUsed: result.totalTokensUsed
|
|
2862
2514
|
};
|
|
2863
2515
|
}
|
|
2864
|
-
async embedBatch(texts) {
|
|
2865
|
-
const token = this.getToken();
|
|
2866
|
-
const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
|
|
2867
|
-
method: "POST",
|
|
2868
|
-
headers: {
|
|
2869
|
-
Authorization: `Bearer ${token}`,
|
|
2870
|
-
"Content-Type": "application/json",
|
|
2871
|
-
Accept: "application/vnd.github+json",
|
|
2872
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
2873
|
-
},
|
|
2874
|
-
body: JSON.stringify({
|
|
2875
|
-
model: `openai/${this.modelInfo.model}`,
|
|
2876
|
-
input: texts
|
|
2877
|
-
})
|
|
2878
|
-
});
|
|
2879
|
-
if (!response.ok) {
|
|
2880
|
-
const error = await response.text();
|
|
2881
|
-
throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
|
|
2882
|
-
}
|
|
2883
|
-
const data = await response.json();
|
|
2884
|
-
return {
|
|
2885
|
-
embeddings: data.data.map((d) => d.embedding),
|
|
2886
|
-
totalTokensUsed: data.usage.total_tokens
|
|
2887
|
-
};
|
|
2888
|
-
}
|
|
2889
2516
|
getModelInfo() {
|
|
2890
2517
|
return this.modelInfo;
|
|
2891
2518
|
}
|
|
2892
2519
|
};
|
|
2893
|
-
var
|
|
2520
|
+
var CustomProviderNonRetryableError = class extends Error {
|
|
2521
|
+
constructor(message) {
|
|
2522
|
+
super(message);
|
|
2523
|
+
this.name = "CustomProviderNonRetryableError";
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
|
|
2527
|
+
// src/utils/url-validation.ts
|
|
2528
|
+
var BLOCKED_METADATA_IPS = [
|
|
2529
|
+
/^169\.254\.169\.254$/,
|
|
2530
|
+
// AWS/Azure/GCP metadata
|
|
2531
|
+
/^169\.254\.170\.2$/,
|
|
2532
|
+
// AWS ECS task metadata
|
|
2533
|
+
/^fd00:ec2::254$/
|
|
2534
|
+
// AWS IMDSv2 IPv6
|
|
2535
|
+
];
|
|
2536
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
2537
|
+
"metadata.google.internal",
|
|
2538
|
+
"metadata.google",
|
|
2539
|
+
"metadata.goog",
|
|
2540
|
+
"kubernetes.default.svc"
|
|
2541
|
+
]);
|
|
2542
|
+
function validateExternalUrl(urlString) {
|
|
2543
|
+
let parsed;
|
|
2544
|
+
try {
|
|
2545
|
+
parsed = new URL(urlString);
|
|
2546
|
+
} catch {
|
|
2547
|
+
return { valid: false, reason: `Invalid URL: ${sanitizeUrlForError(urlString)}` };
|
|
2548
|
+
}
|
|
2549
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2550
|
+
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2551
|
+
}
|
|
2552
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
2553
|
+
if (BLOCKED_HOSTNAMES.has(hostname)) {
|
|
2554
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
|
|
2555
|
+
}
|
|
2556
|
+
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2557
|
+
if (pattern.test(hostname)) {
|
|
2558
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (/^169\.254\./.test(hostname)) {
|
|
2562
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname})` };
|
|
2563
|
+
}
|
|
2564
|
+
return { valid: true };
|
|
2565
|
+
}
|
|
2566
|
+
function sanitizeUrlForError(url) {
|
|
2567
|
+
try {
|
|
2568
|
+
const parsed = new URL(url);
|
|
2569
|
+
parsed.username = "";
|
|
2570
|
+
parsed.password = "";
|
|
2571
|
+
return parsed.toString();
|
|
2572
|
+
} catch {
|
|
2573
|
+
const maxLen = 80;
|
|
2574
|
+
if (url.length > maxLen) {
|
|
2575
|
+
return url.slice(0, maxLen) + "...";
|
|
2576
|
+
}
|
|
2577
|
+
return url;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// src/embeddings/providers/custom.ts
|
|
2582
|
+
var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
|
|
2894
2583
|
constructor(credentials, modelInfo) {
|
|
2895
|
-
|
|
2896
|
-
this.modelInfo = modelInfo;
|
|
2584
|
+
super(credentials, modelInfo);
|
|
2897
2585
|
}
|
|
2898
|
-
|
|
2899
|
-
const
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2586
|
+
splitIntoRequestBatches(texts) {
|
|
2587
|
+
const maxBatchSize = this.modelInfo.maxBatchSize;
|
|
2588
|
+
if (!maxBatchSize || texts.length <= maxBatchSize) {
|
|
2589
|
+
return [texts];
|
|
2590
|
+
}
|
|
2591
|
+
const batches = [];
|
|
2592
|
+
for (let i = 0; i < texts.length; i += maxBatchSize) {
|
|
2593
|
+
batches.push(texts.slice(i, i + maxBatchSize));
|
|
2594
|
+
}
|
|
2595
|
+
return batches;
|
|
2596
|
+
}
|
|
2597
|
+
async embedRequest(texts) {
|
|
2598
|
+
if (texts.length === 0) {
|
|
2599
|
+
return {
|
|
2600
|
+
embeddings: [],
|
|
2601
|
+
totalTokensUsed: 0
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
const headers = {
|
|
2605
|
+
"Content-Type": "application/json"
|
|
2903
2606
|
};
|
|
2607
|
+
if (this.credentials.apiKey) {
|
|
2608
|
+
headers.Authorization = `Bearer ${this.credentials.apiKey}`;
|
|
2609
|
+
}
|
|
2610
|
+
const baseUrl = this.credentials.baseUrl ?? "";
|
|
2611
|
+
const fullUrl = `${baseUrl}/embeddings`;
|
|
2612
|
+
const urlCheck = validateExternalUrl(fullUrl);
|
|
2613
|
+
if (!urlCheck.valid) {
|
|
2614
|
+
throw new CustomProviderNonRetryableError(
|
|
2615
|
+
`Custom embedding provider URL blocked (SSRF protection): ${urlCheck.reason}`
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
const timeoutMs = this.modelInfo.timeoutMs;
|
|
2619
|
+
const controller = new AbortController();
|
|
2620
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2621
|
+
let response;
|
|
2622
|
+
try {
|
|
2623
|
+
response = await fetch(fullUrl, {
|
|
2624
|
+
method: "POST",
|
|
2625
|
+
headers,
|
|
2626
|
+
body: JSON.stringify({
|
|
2627
|
+
model: this.modelInfo.model,
|
|
2628
|
+
input: texts
|
|
2629
|
+
}),
|
|
2630
|
+
signal: controller.signal
|
|
2631
|
+
});
|
|
2632
|
+
} catch (error) {
|
|
2633
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2634
|
+
throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${sanitizeUrlForError(fullUrl)}`);
|
|
2635
|
+
}
|
|
2636
|
+
throw error;
|
|
2637
|
+
} finally {
|
|
2638
|
+
clearTimeout(timeout);
|
|
2639
|
+
}
|
|
2640
|
+
if (!response.ok) {
|
|
2641
|
+
const errorText = (await response.text()).slice(0, 500);
|
|
2642
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
2643
|
+
throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
|
|
2644
|
+
}
|
|
2645
|
+
throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
|
|
2646
|
+
}
|
|
2647
|
+
const data = await response.json();
|
|
2648
|
+
if (data.data && Array.isArray(data.data)) {
|
|
2649
|
+
if (data.data.length > 0) {
|
|
2650
|
+
const actualDims = data.data[0].embedding.length;
|
|
2651
|
+
if (actualDims !== this.modelInfo.dimensions) {
|
|
2652
|
+
throw new Error(
|
|
2653
|
+
`Dimension mismatch: customProvider.dimensions is ${this.modelInfo.dimensions}, but the API returned vectors with ${actualDims} dimensions. Update your config to match the model's actual output dimensions.`
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
if (data.data.length !== texts.length) {
|
|
2658
|
+
throw new Error(
|
|
2659
|
+
`Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
return {
|
|
2663
|
+
embeddings: data.data.map((d) => d.embedding),
|
|
2664
|
+
totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
2904
2668
|
}
|
|
2905
|
-
async
|
|
2906
|
-
const
|
|
2669
|
+
async embedBatch(texts) {
|
|
2670
|
+
const requestBatches = this.splitIntoRequestBatches(texts);
|
|
2671
|
+
const embeddings = [];
|
|
2672
|
+
let totalTokensUsed = 0;
|
|
2673
|
+
for (const batch of requestBatches) {
|
|
2674
|
+
const result = await this.embedRequest(batch);
|
|
2675
|
+
embeddings.push(...result.embeddings);
|
|
2676
|
+
totalTokensUsed += result.totalTokensUsed;
|
|
2677
|
+
}
|
|
2907
2678
|
return {
|
|
2908
|
-
|
|
2909
|
-
|
|
2679
|
+
embeddings,
|
|
2680
|
+
totalTokensUsed
|
|
2910
2681
|
};
|
|
2911
2682
|
}
|
|
2683
|
+
};
|
|
2684
|
+
|
|
2685
|
+
// src/embeddings/providers/github-copilot.ts
|
|
2686
|
+
var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
|
|
2687
|
+
constructor(credentials, modelInfo) {
|
|
2688
|
+
super(credentials, modelInfo);
|
|
2689
|
+
}
|
|
2690
|
+
getToken() {
|
|
2691
|
+
if (!this.credentials.refreshToken) {
|
|
2692
|
+
throw new Error("No OAuth token available for GitHub");
|
|
2693
|
+
}
|
|
2694
|
+
return this.credentials.refreshToken;
|
|
2695
|
+
}
|
|
2912
2696
|
async embedBatch(texts) {
|
|
2913
|
-
const
|
|
2697
|
+
const token = this.getToken();
|
|
2698
|
+
const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
|
|
2914
2699
|
method: "POST",
|
|
2915
2700
|
headers: {
|
|
2916
|
-
Authorization: `Bearer ${
|
|
2917
|
-
"Content-Type": "application/json"
|
|
2701
|
+
Authorization: `Bearer ${token}`,
|
|
2702
|
+
"Content-Type": "application/json",
|
|
2703
|
+
Accept: "application/vnd.github+json",
|
|
2704
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
2918
2705
|
},
|
|
2919
2706
|
body: JSON.stringify({
|
|
2920
|
-
model: this.modelInfo.model
|
|
2707
|
+
model: `openai/${this.modelInfo.model}`,
|
|
2921
2708
|
input: texts
|
|
2922
2709
|
})
|
|
2923
2710
|
});
|
|
2924
2711
|
if (!response.ok) {
|
|
2925
|
-
const error = await response.text();
|
|
2926
|
-
throw new Error(`
|
|
2712
|
+
const error = (await response.text()).slice(0, 500);
|
|
2713
|
+
throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
|
|
2927
2714
|
}
|
|
2928
2715
|
const data = await response.json();
|
|
2929
2716
|
return {
|
|
@@ -2931,16 +2718,14 @@ var OpenAIEmbeddingProvider = class {
|
|
|
2931
2718
|
totalTokensUsed: data.usage.total_tokens
|
|
2932
2719
|
};
|
|
2933
2720
|
}
|
|
2934
|
-
getModelInfo() {
|
|
2935
|
-
return this.modelInfo;
|
|
2936
|
-
}
|
|
2937
2721
|
};
|
|
2938
|
-
|
|
2722
|
+
|
|
2723
|
+
// src/embeddings/providers/google.ts
|
|
2724
|
+
var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
|
|
2725
|
+
static BATCH_SIZE = 20;
|
|
2939
2726
|
constructor(credentials, modelInfo) {
|
|
2940
|
-
|
|
2941
|
-
this.modelInfo = modelInfo;
|
|
2727
|
+
super(credentials, modelInfo);
|
|
2942
2728
|
}
|
|
2943
|
-
static BATCH_SIZE = 20;
|
|
2944
2729
|
async embedQuery(query) {
|
|
2945
2730
|
const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
|
|
2946
2731
|
const result = await this.embedWithTaskType([query], taskType);
|
|
@@ -2961,12 +2746,6 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
|
|
|
2961
2746
|
const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
|
|
2962
2747
|
return this.embedWithTaskType(texts, taskType);
|
|
2963
2748
|
}
|
|
2964
|
-
/**
|
|
2965
|
-
* Embeds texts using the Google embedContent API.
|
|
2966
|
-
* Sends multiple texts as parts in batched requests (up to BATCH_SIZE per call).
|
|
2967
|
-
* When taskType is provided (gemini-embedding-001), includes it in the request
|
|
2968
|
-
* for task-specific embedding optimization.
|
|
2969
|
-
*/
|
|
2970
2749
|
async embedWithTaskType(texts, taskType) {
|
|
2971
2750
|
const batches = [];
|
|
2972
2751
|
for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
|
|
@@ -2983,17 +2762,18 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
|
|
|
2983
2762
|
outputDimensionality: this.modelInfo.dimensions
|
|
2984
2763
|
}));
|
|
2985
2764
|
const response = await fetch(
|
|
2986
|
-
`${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents
|
|
2765
|
+
`${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents`,
|
|
2987
2766
|
{
|
|
2988
2767
|
method: "POST",
|
|
2989
2768
|
headers: {
|
|
2990
|
-
"Content-Type": "application/json"
|
|
2769
|
+
"Content-Type": "application/json",
|
|
2770
|
+
...this.credentials.apiKey && { "x-goog-api-key": this.credentials.apiKey }
|
|
2991
2771
|
},
|
|
2992
2772
|
body: JSON.stringify({ requests })
|
|
2993
2773
|
}
|
|
2994
2774
|
);
|
|
2995
2775
|
if (!response.ok) {
|
|
2996
|
-
const error = await response.text();
|
|
2776
|
+
const error = (await response.text()).slice(0, 500);
|
|
2997
2777
|
throw new Error(`Google embedding API error: ${response.status} - ${error}`);
|
|
2998
2778
|
}
|
|
2999
2779
|
const data = await response.json();
|
|
@@ -3008,29 +2788,13 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
|
|
|
3008
2788
|
totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
|
|
3009
2789
|
};
|
|
3010
2790
|
}
|
|
3011
|
-
getModelInfo() {
|
|
3012
|
-
return this.modelInfo;
|
|
3013
|
-
}
|
|
3014
2791
|
};
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
this.modelInfo = modelInfo;
|
|
3019
|
-
}
|
|
2792
|
+
|
|
2793
|
+
// src/embeddings/providers/ollama.ts
|
|
2794
|
+
var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
|
|
3020
2795
|
static MIN_TRUNCATION_CHARS = 512;
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
return {
|
|
3024
|
-
embedding: result.embeddings[0],
|
|
3025
|
-
tokensUsed: result.totalTokensUsed
|
|
3026
|
-
};
|
|
3027
|
-
}
|
|
3028
|
-
async embedDocument(document) {
|
|
3029
|
-
const result = await this.embedBatch([document]);
|
|
3030
|
-
return {
|
|
3031
|
-
embedding: result.embeddings[0],
|
|
3032
|
-
tokensUsed: result.totalTokensUsed
|
|
3033
|
-
};
|
|
2796
|
+
constructor(credentials, modelInfo) {
|
|
2797
|
+
super(credentials, modelInfo);
|
|
3034
2798
|
}
|
|
3035
2799
|
estimateTokens(text) {
|
|
3036
2800
|
return Math.ceil(text.length / 4);
|
|
@@ -3115,7 +2879,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
|
|
|
3115
2879
|
})
|
|
3116
2880
|
});
|
|
3117
2881
|
if (!response.ok) {
|
|
3118
|
-
const error = await response.text();
|
|
2882
|
+
const error = (await response.text()).slice(0, 500);
|
|
3119
2883
|
throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
|
|
3120
2884
|
}
|
|
3121
2885
|
const data = await response.json();
|
|
@@ -3134,125 +2898,56 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
|
|
|
3134
2898
|
totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
|
|
3135
2899
|
};
|
|
3136
2900
|
}
|
|
3137
|
-
getModelInfo() {
|
|
3138
|
-
return this.modelInfo;
|
|
3139
|
-
}
|
|
3140
2901
|
};
|
|
3141
|
-
|
|
2902
|
+
|
|
2903
|
+
// src/embeddings/providers/openai.ts
|
|
2904
|
+
var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
|
|
3142
2905
|
constructor(credentials, modelInfo) {
|
|
3143
|
-
|
|
3144
|
-
this.modelInfo = modelInfo;
|
|
2906
|
+
super(credentials, modelInfo);
|
|
3145
2907
|
}
|
|
3146
|
-
|
|
3147
|
-
const
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
if (texts.length === 0) {
|
|
3159
|
-
return {
|
|
3160
|
-
embeddings: [],
|
|
3161
|
-
totalTokensUsed: 0
|
|
3162
|
-
};
|
|
3163
|
-
}
|
|
3164
|
-
const headers = {
|
|
3165
|
-
"Content-Type": "application/json"
|
|
3166
|
-
};
|
|
3167
|
-
if (this.credentials.apiKey) {
|
|
3168
|
-
headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
|
|
3169
|
-
}
|
|
3170
|
-
const baseUrl = this.credentials.baseUrl ?? "";
|
|
3171
|
-
const timeoutMs = this.modelInfo.timeoutMs;
|
|
3172
|
-
const controller = new AbortController();
|
|
3173
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
3174
|
-
let response;
|
|
3175
|
-
try {
|
|
3176
|
-
response = await fetch(`${baseUrl}/embeddings`, {
|
|
3177
|
-
method: "POST",
|
|
3178
|
-
headers,
|
|
3179
|
-
body: JSON.stringify({
|
|
3180
|
-
model: this.modelInfo.model,
|
|
3181
|
-
input: texts
|
|
3182
|
-
}),
|
|
3183
|
-
signal: controller.signal
|
|
3184
|
-
});
|
|
3185
|
-
} catch (error) {
|
|
3186
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
3187
|
-
throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
|
|
3188
|
-
}
|
|
3189
|
-
throw error;
|
|
3190
|
-
} finally {
|
|
3191
|
-
clearTimeout(timeout);
|
|
3192
|
-
}
|
|
2908
|
+
async embedBatch(texts) {
|
|
2909
|
+
const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
|
|
2910
|
+
method: "POST",
|
|
2911
|
+
headers: {
|
|
2912
|
+
Authorization: `Bearer ${this.credentials.apiKey}`,
|
|
2913
|
+
"Content-Type": "application/json"
|
|
2914
|
+
},
|
|
2915
|
+
body: JSON.stringify({
|
|
2916
|
+
model: this.modelInfo.model,
|
|
2917
|
+
input: texts
|
|
2918
|
+
})
|
|
2919
|
+
});
|
|
3193
2920
|
if (!response.ok) {
|
|
3194
|
-
const
|
|
3195
|
-
|
|
3196
|
-
throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
|
|
3197
|
-
}
|
|
3198
|
-
throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
|
|
2921
|
+
const error = (await response.text()).slice(0, 500);
|
|
2922
|
+
throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
|
|
3199
2923
|
}
|
|
3200
2924
|
const data = await response.json();
|
|
3201
|
-
if (data.data && Array.isArray(data.data)) {
|
|
3202
|
-
if (data.data.length > 0) {
|
|
3203
|
-
const actualDims = data.data[0].embedding.length;
|
|
3204
|
-
if (actualDims !== this.modelInfo.dimensions) {
|
|
3205
|
-
throw new Error(
|
|
3206
|
-
`Dimension mismatch: customProvider.dimensions is ${this.modelInfo.dimensions}, but the API returned vectors with ${actualDims} dimensions. Update your config to match the model's actual output dimensions.`
|
|
3207
|
-
);
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
if (data.data.length !== texts.length) {
|
|
3211
|
-
throw new Error(
|
|
3212
|
-
`Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
|
|
3213
|
-
);
|
|
3214
|
-
}
|
|
3215
|
-
return {
|
|
3216
|
-
embeddings: data.data.map((d) => d.embedding),
|
|
3217
|
-
// Rough estimate: ~4 chars per token. Used as fallback when the server
|
|
3218
|
-
// doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
|
|
3219
|
-
totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
|
|
3220
|
-
};
|
|
3221
|
-
}
|
|
3222
|
-
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
3223
|
-
}
|
|
3224
|
-
async embedQuery(query) {
|
|
3225
|
-
const result = await this.embedBatch([query]);
|
|
3226
|
-
return {
|
|
3227
|
-
embedding: result.embeddings[0],
|
|
3228
|
-
tokensUsed: result.totalTokensUsed
|
|
3229
|
-
};
|
|
3230
|
-
}
|
|
3231
|
-
async embedDocument(document) {
|
|
3232
|
-
const result = await this.embedBatch([document]);
|
|
3233
2925
|
return {
|
|
3234
|
-
|
|
3235
|
-
|
|
2926
|
+
embeddings: data.data.map((d) => d.embedding),
|
|
2927
|
+
totalTokensUsed: data.usage.total_tokens
|
|
3236
2928
|
};
|
|
3237
2929
|
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
// src/embeddings/provider.ts
|
|
2933
|
+
function createEmbeddingProvider(configuredProviderInfo) {
|
|
2934
|
+
switch (configuredProviderInfo.provider) {
|
|
2935
|
+
case "github-copilot":
|
|
2936
|
+
return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2937
|
+
case "openai":
|
|
2938
|
+
return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2939
|
+
case "google":
|
|
2940
|
+
return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2941
|
+
case "ollama":
|
|
2942
|
+
return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2943
|
+
case "custom":
|
|
2944
|
+
return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
|
|
2945
|
+
default: {
|
|
2946
|
+
const _exhaustive = configuredProviderInfo;
|
|
2947
|
+
throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
|
|
3246
2948
|
}
|
|
3247
|
-
return {
|
|
3248
|
-
embeddings,
|
|
3249
|
-
totalTokensUsed
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
getModelInfo() {
|
|
3253
|
-
return this.modelInfo;
|
|
3254
2949
|
}
|
|
3255
|
-
}
|
|
2950
|
+
}
|
|
3256
2951
|
|
|
3257
2952
|
// src/rerank/index.ts
|
|
3258
2953
|
function createReranker(config) {
|
|
@@ -3289,7 +2984,10 @@ var SiliconFlowReranker = class {
|
|
|
3289
2984
|
if (this.config.apiKey) {
|
|
3290
2985
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
3291
2986
|
}
|
|
3292
|
-
const baseUrl = this.config.baseUrl
|
|
2987
|
+
const baseUrl = this.config.baseUrl;
|
|
2988
|
+
if (!baseUrl) {
|
|
2989
|
+
throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
|
|
2990
|
+
}
|
|
3293
2991
|
const timeoutMs = this.config.timeoutMs ?? 3e4;
|
|
3294
2992
|
const controller = new AbortController();
|
|
3295
2993
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -3332,8 +3030,22 @@ var SiliconFlowReranker = class {
|
|
|
3332
3030
|
|
|
3333
3031
|
// src/utils/files.ts
|
|
3334
3032
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
3335
|
-
var
|
|
3336
|
-
var
|
|
3033
|
+
var import_fs3 = require("fs");
|
|
3034
|
+
var path4 = __toESM(require("path"), 1);
|
|
3035
|
+
|
|
3036
|
+
// src/utils/paths.ts
|
|
3037
|
+
var path3 = __toESM(require("path"), 1);
|
|
3038
|
+
function normalizePathSeparators(value) {
|
|
3039
|
+
return value.replace(/\\/g, "/");
|
|
3040
|
+
}
|
|
3041
|
+
function isHiddenPathSegment(part) {
|
|
3042
|
+
return part.startsWith(".") && part !== "." && part !== "..";
|
|
3043
|
+
}
|
|
3044
|
+
function isBuildPathSegment(part) {
|
|
3045
|
+
return part.toLowerCase().includes("build");
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/utils/files.ts
|
|
3337
3049
|
function createIgnoreFilter(projectRoot) {
|
|
3338
3050
|
const ig = (0, import_ignore.default)();
|
|
3339
3051
|
const defaultIgnores = [
|
|
@@ -3354,9 +3066,9 @@ function createIgnoreFilter(projectRoot) {
|
|
|
3354
3066
|
"**/*build*/**"
|
|
3355
3067
|
];
|
|
3356
3068
|
ig.add(defaultIgnores);
|
|
3357
|
-
const gitignorePath =
|
|
3358
|
-
if ((0,
|
|
3359
|
-
const gitignoreContent = (0,
|
|
3069
|
+
const gitignorePath = path4.join(projectRoot, ".gitignore");
|
|
3070
|
+
if ((0, import_fs3.existsSync)(gitignorePath)) {
|
|
3071
|
+
const gitignoreContent = (0, import_fs3.readFileSync)(gitignorePath, "utf-8");
|
|
3360
3072
|
ig.add(gitignoreContent);
|
|
3361
3073
|
}
|
|
3362
3074
|
return ig;
|
|
@@ -3377,19 +3089,19 @@ function matchGlob(filePath, pattern) {
|
|
|
3377
3089
|
return regex.test(filePath);
|
|
3378
3090
|
}
|
|
3379
3091
|
async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
|
|
3380
|
-
const entries = await
|
|
3092
|
+
const entries = await import_fs3.promises.readdir(dir, { withFileTypes: true });
|
|
3381
3093
|
const filesInDir = [];
|
|
3382
3094
|
const subdirs = [];
|
|
3383
3095
|
for (const entry of entries) {
|
|
3384
|
-
const fullPath =
|
|
3385
|
-
const relativePath =
|
|
3386
|
-
if (
|
|
3096
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3097
|
+
const relativePath = path4.relative(projectRoot, fullPath);
|
|
3098
|
+
if (isHiddenPathSegment(entry.name)) {
|
|
3387
3099
|
if (entry.isDirectory()) {
|
|
3388
3100
|
skipped.push({ path: relativePath, reason: "excluded" });
|
|
3389
3101
|
}
|
|
3390
3102
|
continue;
|
|
3391
3103
|
}
|
|
3392
|
-
if (entry.isDirectory() && entry.name
|
|
3104
|
+
if (entry.isDirectory() && isBuildPathSegment(entry.name)) {
|
|
3393
3105
|
skipped.push({ path: relativePath, reason: "excluded" });
|
|
3394
3106
|
continue;
|
|
3395
3107
|
}
|
|
@@ -3402,7 +3114,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
3402
3114
|
if (entry.isDirectory()) {
|
|
3403
3115
|
subdirs.push({ fullPath, relativePath });
|
|
3404
3116
|
} else if (entry.isFile()) {
|
|
3405
|
-
const stat = await
|
|
3117
|
+
const stat = await import_fs3.promises.stat(fullPath);
|
|
3406
3118
|
if (stat.size > maxFileSize) {
|
|
3407
3119
|
skipped.push({ path: relativePath, reason: "too_large" });
|
|
3408
3120
|
continue;
|
|
@@ -3431,7 +3143,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
3431
3143
|
yield f;
|
|
3432
3144
|
}
|
|
3433
3145
|
for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
|
|
3434
|
-
skipped.push({ path:
|
|
3146
|
+
skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
|
|
3435
3147
|
}
|
|
3436
3148
|
const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
|
|
3437
3149
|
if (canRecurse) {
|
|
@@ -3471,14 +3183,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
|
|
|
3471
3183
|
if (additionalRoots && additionalRoots.length > 0) {
|
|
3472
3184
|
const normalizedRoots = /* @__PURE__ */ new Set();
|
|
3473
3185
|
for (const kbRoot of additionalRoots) {
|
|
3474
|
-
const resolved =
|
|
3475
|
-
|
|
3186
|
+
const resolved = path4.normalize(
|
|
3187
|
+
path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
|
|
3476
3188
|
);
|
|
3477
3189
|
normalizedRoots.add(resolved);
|
|
3478
3190
|
}
|
|
3479
3191
|
for (const resolvedKbRoot of normalizedRoots) {
|
|
3480
3192
|
try {
|
|
3481
|
-
const stat = await
|
|
3193
|
+
const stat = await import_fs3.promises.stat(resolvedKbRoot);
|
|
3482
3194
|
if (!stat.isDirectory()) {
|
|
3483
3195
|
skipped.push({ path: resolvedKbRoot, reason: "excluded" });
|
|
3484
3196
|
continue;
|
|
@@ -3541,20 +3253,21 @@ function createCostEstimate(files, provider) {
|
|
|
3541
3253
|
}
|
|
3542
3254
|
function formatCostEstimate(estimate) {
|
|
3543
3255
|
const sizeFormatted = formatBytes(estimate.totalSizeBytes);
|
|
3256
|
+
const filesFormatted = `${estimate.filesCount.toLocaleString()} files`;
|
|
3544
3257
|
const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
|
|
3545
3258
|
return `
|
|
3546
3259
|
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
3547
3260
|
\u2502 \u{1F4CA} Indexing Estimate \u2502
|
|
3548
3261
|
\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
|
|
3549
3262
|
\u2502 \u2502
|
|
3550
|
-
\u2502 Files to index: ${
|
|
3551
|
-
\u2502 Total size: ${
|
|
3552
|
-
\u2502 Estimated chunks: ${
|
|
3553
|
-
\u2502 Estimated tokens: ${
|
|
3263
|
+
\u2502 Files to index: ${filesFormatted.padEnd(40)}\u2502
|
|
3264
|
+
\u2502 Total size: ${sizeFormatted.padEnd(40)}\u2502
|
|
3265
|
+
\u2502 Estimated chunks: ${("~" + estimate.estimatedChunks.toLocaleString() + " chunks").padEnd(40)}\u2502
|
|
3266
|
+
\u2502 Estimated tokens: ${("~" + estimate.estimatedTokens.toLocaleString() + " tokens").padEnd(40)}\u2502
|
|
3554
3267
|
\u2502 \u2502
|
|
3555
|
-
\u2502 Provider: ${
|
|
3556
|
-
\u2502 Model: ${
|
|
3557
|
-
\u2502 Cost: ${
|
|
3268
|
+
\u2502 Provider: ${estimate.provider.padEnd(52)}\u2502
|
|
3269
|
+
\u2502 Model: ${estimate.model.padEnd(52)}\u2502
|
|
3270
|
+
\u2502 Cost: ${costFormatted.padEnd(52)}\u2502
|
|
3558
3271
|
\u2502 \u2502
|
|
3559
3272
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
3560
3273
|
`;
|
|
@@ -3566,9 +3279,6 @@ function formatBytes(bytes) {
|
|
|
3566
3279
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3567
3280
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
3568
3281
|
}
|
|
3569
|
-
function padRight(str, length) {
|
|
3570
|
-
return str.padEnd(length);
|
|
3571
|
-
}
|
|
3572
3282
|
|
|
3573
3283
|
// src/utils/logger.ts
|
|
3574
3284
|
var LOG_LEVEL_PRIORITY = {
|
|
@@ -3635,6 +3345,10 @@ var Logger = class {
|
|
|
3635
3345
|
this.logs.shift();
|
|
3636
3346
|
}
|
|
3637
3347
|
}
|
|
3348
|
+
withMetrics(fn) {
|
|
3349
|
+
if (!this.config.metrics) return;
|
|
3350
|
+
fn();
|
|
3351
|
+
}
|
|
3638
3352
|
search(level, message, data) {
|
|
3639
3353
|
if (this.config.logSearch) {
|
|
3640
3354
|
this.log(level, "search", message, data);
|
|
@@ -3673,89 +3387,107 @@ var Logger = class {
|
|
|
3673
3387
|
this.log("debug", "general", message, data);
|
|
3674
3388
|
}
|
|
3675
3389
|
recordIndexingStart() {
|
|
3676
|
-
|
|
3677
|
-
|
|
3390
|
+
this.withMetrics(() => {
|
|
3391
|
+
this.metrics.indexingStartTime = Date.now();
|
|
3392
|
+
});
|
|
3678
3393
|
}
|
|
3679
3394
|
recordIndexingEnd() {
|
|
3680
|
-
|
|
3681
|
-
|
|
3395
|
+
this.withMetrics(() => {
|
|
3396
|
+
this.metrics.indexingEndTime = Date.now();
|
|
3397
|
+
});
|
|
3682
3398
|
}
|
|
3683
3399
|
recordFilesScanned(count) {
|
|
3684
|
-
|
|
3685
|
-
|
|
3400
|
+
this.withMetrics(() => {
|
|
3401
|
+
this.metrics.filesScanned = count;
|
|
3402
|
+
});
|
|
3686
3403
|
}
|
|
3687
3404
|
recordFilesParsed(count) {
|
|
3688
|
-
|
|
3689
|
-
|
|
3405
|
+
this.withMetrics(() => {
|
|
3406
|
+
this.metrics.filesParsed = count;
|
|
3407
|
+
});
|
|
3690
3408
|
}
|
|
3691
3409
|
recordParseDuration(durationMs) {
|
|
3692
|
-
|
|
3693
|
-
|
|
3410
|
+
this.withMetrics(() => {
|
|
3411
|
+
this.metrics.parseMs = durationMs;
|
|
3412
|
+
});
|
|
3694
3413
|
}
|
|
3695
3414
|
recordChunksProcessed(count) {
|
|
3696
|
-
|
|
3697
|
-
|
|
3415
|
+
this.withMetrics(() => {
|
|
3416
|
+
this.metrics.chunksProcessed += count;
|
|
3417
|
+
});
|
|
3698
3418
|
}
|
|
3699
3419
|
recordChunksEmbedded(count) {
|
|
3700
|
-
|
|
3701
|
-
|
|
3420
|
+
this.withMetrics(() => {
|
|
3421
|
+
this.metrics.chunksEmbedded += count;
|
|
3422
|
+
});
|
|
3702
3423
|
}
|
|
3703
3424
|
recordChunksFromCache(count) {
|
|
3704
|
-
|
|
3705
|
-
|
|
3425
|
+
this.withMetrics(() => {
|
|
3426
|
+
this.metrics.chunksFromCache += count;
|
|
3427
|
+
});
|
|
3706
3428
|
}
|
|
3707
3429
|
recordChunksRemoved(count) {
|
|
3708
|
-
|
|
3709
|
-
|
|
3430
|
+
this.withMetrics(() => {
|
|
3431
|
+
this.metrics.chunksRemoved += count;
|
|
3432
|
+
});
|
|
3710
3433
|
}
|
|
3711
3434
|
recordEmbeddingApiCall(tokens) {
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3435
|
+
this.withMetrics(() => {
|
|
3436
|
+
this.metrics.embeddingApiCalls++;
|
|
3437
|
+
this.metrics.embeddingTokensUsed += tokens;
|
|
3438
|
+
});
|
|
3715
3439
|
}
|
|
3716
3440
|
recordEmbeddingError() {
|
|
3717
|
-
|
|
3718
|
-
|
|
3441
|
+
this.withMetrics(() => {
|
|
3442
|
+
this.metrics.embeddingErrors++;
|
|
3443
|
+
});
|
|
3719
3444
|
}
|
|
3720
3445
|
recordSearch(durationMs, breakdown) {
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3446
|
+
this.withMetrics(() => {
|
|
3447
|
+
this.metrics.searchCount++;
|
|
3448
|
+
this.metrics.searchTotalMs += durationMs;
|
|
3449
|
+
this.metrics.searchLastMs = durationMs;
|
|
3450
|
+
this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
|
|
3451
|
+
if (breakdown) {
|
|
3452
|
+
this.metrics.embeddingCallMs = breakdown.embeddingMs;
|
|
3453
|
+
this.metrics.vectorSearchMs = breakdown.vectorMs;
|
|
3454
|
+
this.metrics.keywordSearchMs = breakdown.keywordMs;
|
|
3455
|
+
this.metrics.fusionMs = breakdown.fusionMs;
|
|
3456
|
+
}
|
|
3457
|
+
});
|
|
3732
3458
|
}
|
|
3733
3459
|
recordCacheHit() {
|
|
3734
|
-
|
|
3735
|
-
|
|
3460
|
+
this.withMetrics(() => {
|
|
3461
|
+
this.metrics.cacheHits++;
|
|
3462
|
+
});
|
|
3736
3463
|
}
|
|
3737
3464
|
recordCacheMiss() {
|
|
3738
|
-
|
|
3739
|
-
|
|
3465
|
+
this.withMetrics(() => {
|
|
3466
|
+
this.metrics.cacheMisses++;
|
|
3467
|
+
});
|
|
3740
3468
|
}
|
|
3741
3469
|
recordQueryCacheHit() {
|
|
3742
|
-
|
|
3743
|
-
|
|
3470
|
+
this.withMetrics(() => {
|
|
3471
|
+
this.metrics.queryCacheHits++;
|
|
3472
|
+
});
|
|
3744
3473
|
}
|
|
3745
3474
|
recordQueryCacheSimilarHit() {
|
|
3746
|
-
|
|
3747
|
-
|
|
3475
|
+
this.withMetrics(() => {
|
|
3476
|
+
this.metrics.queryCacheSimilarHits++;
|
|
3477
|
+
});
|
|
3748
3478
|
}
|
|
3749
3479
|
recordQueryCacheMiss() {
|
|
3750
|
-
|
|
3751
|
-
|
|
3480
|
+
this.withMetrics(() => {
|
|
3481
|
+
this.metrics.queryCacheMisses++;
|
|
3482
|
+
});
|
|
3752
3483
|
}
|
|
3753
3484
|
recordGc(orphans, chunks, embeddings) {
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3485
|
+
this.withMetrics(() => {
|
|
3486
|
+
this.metrics.gcRuns++;
|
|
3487
|
+
this.metrics.gcOrphansRemoved += orphans;
|
|
3488
|
+
this.metrics.gcChunksRemoved += chunks;
|
|
3489
|
+
this.metrics.gcEmbeddingsRemoved += embeddings;
|
|
3490
|
+
});
|
|
3759
3491
|
}
|
|
3760
3492
|
getMetrics() {
|
|
3761
3493
|
return { ...this.metrics };
|
|
@@ -3862,14 +3594,14 @@ function initializeLogger(config) {
|
|
|
3862
3594
|
}
|
|
3863
3595
|
|
|
3864
3596
|
// src/native/index.ts
|
|
3865
|
-
var
|
|
3866
|
-
var
|
|
3597
|
+
var path5 = __toESM(require("path"), 1);
|
|
3598
|
+
var os2 = __toESM(require("os"), 1);
|
|
3867
3599
|
var module2 = __toESM(require("module"), 1);
|
|
3868
3600
|
var import_url = require("url");
|
|
3869
3601
|
var import_meta = {};
|
|
3870
3602
|
function getNativeBinding() {
|
|
3871
|
-
const platform2 =
|
|
3872
|
-
const arch2 =
|
|
3603
|
+
const platform2 = os2.platform();
|
|
3604
|
+
const arch2 = os2.arch();
|
|
3873
3605
|
let bindingName;
|
|
3874
3606
|
if (platform2 === "darwin" && arch2 === "arm64") {
|
|
3875
3607
|
bindingName = "codebase-index-native.darwin-arm64.node";
|
|
@@ -3887,19 +3619,19 @@ function getNativeBinding() {
|
|
|
3887
3619
|
let currentDir;
|
|
3888
3620
|
let requireTarget;
|
|
3889
3621
|
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
3890
|
-
currentDir =
|
|
3622
|
+
currentDir = path5.dirname((0, import_url.fileURLToPath)(import_meta.url));
|
|
3891
3623
|
requireTarget = import_meta.url;
|
|
3892
3624
|
} else if (typeof __dirname !== "undefined") {
|
|
3893
3625
|
currentDir = __dirname;
|
|
3894
3626
|
requireTarget = __filename;
|
|
3895
3627
|
} else {
|
|
3896
3628
|
currentDir = process.cwd();
|
|
3897
|
-
requireTarget =
|
|
3629
|
+
requireTarget = path5.join(currentDir, "index.js");
|
|
3898
3630
|
}
|
|
3899
3631
|
const normalizedDir = currentDir.replace(/\\/g, "/");
|
|
3900
|
-
const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(
|
|
3901
|
-
const packageRoot = isDevMode ?
|
|
3902
|
-
const nativePath =
|
|
3632
|
+
const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path5.join("src", "native"));
|
|
3633
|
+
const packageRoot = isDevMode ? path5.resolve(currentDir, "../..") : path5.resolve(currentDir, "..");
|
|
3634
|
+
const nativePath = path5.join(packageRoot, "native", bindingName);
|
|
3903
3635
|
const require2 = module2.createRequire(requireTarget);
|
|
3904
3636
|
return require2(nativePath);
|
|
3905
3637
|
}
|
|
@@ -4469,7 +4201,6 @@ var Database = class {
|
|
|
4469
4201
|
this.throwIfClosed();
|
|
4470
4202
|
return this.inner.getStats();
|
|
4471
4203
|
}
|
|
4472
|
-
// ── Symbol methods ──────────────────────────────────────────────
|
|
4473
4204
|
upsertSymbol(symbol) {
|
|
4474
4205
|
this.throwIfClosed();
|
|
4475
4206
|
this.inner.upsertSymbol(symbol);
|
|
@@ -4499,7 +4230,6 @@ var Database = class {
|
|
|
4499
4230
|
this.throwIfClosed();
|
|
4500
4231
|
return this.inner.deleteSymbolsByFile(filePath);
|
|
4501
4232
|
}
|
|
4502
|
-
// ── Call Edge methods ────────────────────────────────────────────
|
|
4503
4233
|
upsertCallEdge(edge) {
|
|
4504
4234
|
this.throwIfClosed();
|
|
4505
4235
|
this.inner.upsertCallEdge(edge);
|
|
@@ -4521,60 +4251,229 @@ var Database = class {
|
|
|
4521
4251
|
this.throwIfClosed();
|
|
4522
4252
|
return this.inner.getCallees(symbolId, branch);
|
|
4523
4253
|
}
|
|
4524
|
-
deleteCallEdgesByFile(filePath) {
|
|
4525
|
-
this.throwIfClosed();
|
|
4526
|
-
return this.inner.deleteCallEdgesByFile(filePath);
|
|
4254
|
+
deleteCallEdgesByFile(filePath) {
|
|
4255
|
+
this.throwIfClosed();
|
|
4256
|
+
return this.inner.deleteCallEdgesByFile(filePath);
|
|
4257
|
+
}
|
|
4258
|
+
resolveCallEdge(edgeId, toSymbolId) {
|
|
4259
|
+
this.throwIfClosed();
|
|
4260
|
+
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
4261
|
+
}
|
|
4262
|
+
addSymbolsToBranch(branch, symbolIds) {
|
|
4263
|
+
this.throwIfClosed();
|
|
4264
|
+
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
4265
|
+
}
|
|
4266
|
+
addSymbolsToBranchBatch(branch, symbolIds) {
|
|
4267
|
+
this.throwIfClosed();
|
|
4268
|
+
if (symbolIds.length === 0) return;
|
|
4269
|
+
this.inner.addSymbolsToBranchBatch(branch, symbolIds);
|
|
4270
|
+
}
|
|
4271
|
+
getBranchSymbolIds(branch) {
|
|
4272
|
+
this.throwIfClosed();
|
|
4273
|
+
return this.inner.getBranchSymbolIds(branch);
|
|
4274
|
+
}
|
|
4275
|
+
clearBranchSymbols(branch) {
|
|
4276
|
+
this.throwIfClosed();
|
|
4277
|
+
return this.inner.clearBranchSymbols(branch);
|
|
4278
|
+
}
|
|
4279
|
+
getReferencedSymbolIds(symbolIds) {
|
|
4280
|
+
this.throwIfClosed();
|
|
4281
|
+
if (symbolIds.length === 0) return [];
|
|
4282
|
+
return this.inner.getReferencedSymbolIds(symbolIds);
|
|
4283
|
+
}
|
|
4284
|
+
deleteBranchSymbolsBySymbolIds(symbolIds) {
|
|
4285
|
+
this.throwIfClosed();
|
|
4286
|
+
if (symbolIds.length === 0) return 0;
|
|
4287
|
+
return this.inner.deleteBranchSymbolsBySymbolIds(symbolIds);
|
|
4288
|
+
}
|
|
4289
|
+
deleteBranchSymbolsForBranch(branch, symbolIds) {
|
|
4290
|
+
this.throwIfClosed();
|
|
4291
|
+
if (symbolIds.length === 0) return 0;
|
|
4292
|
+
return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
|
|
4293
|
+
}
|
|
4294
|
+
gcOrphanSymbols() {
|
|
4295
|
+
this.throwIfClosed();
|
|
4296
|
+
return this.inner.gcOrphanSymbols();
|
|
4297
|
+
}
|
|
4298
|
+
gcOrphanCallEdges() {
|
|
4299
|
+
this.throwIfClosed();
|
|
4300
|
+
return this.inner.gcOrphanCallEdges();
|
|
4301
|
+
}
|
|
4302
|
+
};
|
|
4303
|
+
|
|
4304
|
+
// src/git/index.ts
|
|
4305
|
+
var import_fs5 = require("fs");
|
|
4306
|
+
var path7 = __toESM(require("path"), 1);
|
|
4307
|
+
|
|
4308
|
+
// src/git/refs.ts
|
|
4309
|
+
var import_fs4 = require("fs");
|
|
4310
|
+
var path6 = __toESM(require("path"), 1);
|
|
4311
|
+
function readPackedRefs(gitDir) {
|
|
4312
|
+
const packedRefsPath = path6.join(gitDir, "packed-refs");
|
|
4313
|
+
if (!(0, import_fs4.existsSync)(packedRefsPath)) {
|
|
4314
|
+
return [];
|
|
4315
|
+
}
|
|
4316
|
+
try {
|
|
4317
|
+
return (0, import_fs4.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
4318
|
+
} catch {
|
|
4319
|
+
return [];
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
function resolveCommonGitDir(gitDir) {
|
|
4323
|
+
const commonDirPath = path6.join(gitDir, "commondir");
|
|
4324
|
+
if (!(0, import_fs4.existsSync)(commonDirPath)) {
|
|
4325
|
+
return gitDir;
|
|
4326
|
+
}
|
|
4327
|
+
try {
|
|
4328
|
+
const raw = (0, import_fs4.readFileSync)(commonDirPath, "utf-8").trim();
|
|
4329
|
+
if (!raw) {
|
|
4330
|
+
return gitDir;
|
|
4331
|
+
}
|
|
4332
|
+
const resolved = path6.isAbsolute(raw) ? raw : path6.resolve(gitDir, raw);
|
|
4333
|
+
if ((0, import_fs4.existsSync)(resolved)) {
|
|
4334
|
+
return resolved;
|
|
4335
|
+
}
|
|
4336
|
+
} catch {
|
|
4337
|
+
return gitDir;
|
|
4338
|
+
}
|
|
4339
|
+
return gitDir;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
// src/git/index.ts
|
|
4343
|
+
function resolveWorktreeMainRepoRoot(repoRoot) {
|
|
4344
|
+
const gitDir = resolveGitDir(repoRoot);
|
|
4345
|
+
if (!gitDir) {
|
|
4346
|
+
return null;
|
|
4347
|
+
}
|
|
4348
|
+
const commonGitDir = resolveCommonGitDir(gitDir);
|
|
4349
|
+
if (commonGitDir === gitDir || path7.basename(commonGitDir) !== ".git") {
|
|
4350
|
+
return null;
|
|
4351
|
+
}
|
|
4352
|
+
const mainRepoRoot = path7.dirname(commonGitDir);
|
|
4353
|
+
if (!(0, import_fs5.existsSync)(mainRepoRoot)) {
|
|
4354
|
+
return null;
|
|
4355
|
+
}
|
|
4356
|
+
return path7.resolve(mainRepoRoot) === path7.resolve(repoRoot) ? null : mainRepoRoot;
|
|
4357
|
+
}
|
|
4358
|
+
function resolveGitDir(repoRoot) {
|
|
4359
|
+
const gitPath = path7.join(repoRoot, ".git");
|
|
4360
|
+
if (!(0, import_fs5.existsSync)(gitPath)) {
|
|
4361
|
+
return null;
|
|
4362
|
+
}
|
|
4363
|
+
try {
|
|
4364
|
+
const stat = (0, import_fs5.statSync)(gitPath);
|
|
4365
|
+
if (stat.isDirectory()) {
|
|
4366
|
+
return gitPath;
|
|
4367
|
+
}
|
|
4368
|
+
if (stat.isFile()) {
|
|
4369
|
+
const content = (0, import_fs5.readFileSync)(gitPath, "utf-8").trim();
|
|
4370
|
+
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
4371
|
+
if (match) {
|
|
4372
|
+
const gitdir = match[1];
|
|
4373
|
+
const resolvedPath = path7.isAbsolute(gitdir) ? gitdir : path7.resolve(repoRoot, gitdir);
|
|
4374
|
+
if ((0, import_fs5.existsSync)(resolvedPath)) {
|
|
4375
|
+
return resolvedPath;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
} catch {
|
|
4527
4380
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4381
|
+
return null;
|
|
4382
|
+
}
|
|
4383
|
+
function isGitRepo(dir) {
|
|
4384
|
+
return resolveGitDir(dir) !== null;
|
|
4385
|
+
}
|
|
4386
|
+
function getCurrentBranch(repoRoot) {
|
|
4387
|
+
const gitDir = resolveGitDir(repoRoot);
|
|
4388
|
+
if (!gitDir) {
|
|
4389
|
+
return null;
|
|
4531
4390
|
}
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
4391
|
+
const headPath = path7.join(gitDir, "HEAD");
|
|
4392
|
+
if (!(0, import_fs5.existsSync)(headPath)) {
|
|
4393
|
+
return null;
|
|
4536
4394
|
}
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4395
|
+
try {
|
|
4396
|
+
const headContent = (0, import_fs5.readFileSync)(headPath, "utf-8").trim();
|
|
4397
|
+
const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
|
|
4398
|
+
if (match) {
|
|
4399
|
+
return match[1];
|
|
4400
|
+
}
|
|
4401
|
+
if (/^[0-9a-f]{40}$/i.test(headContent)) {
|
|
4402
|
+
return headContent.slice(0, 7);
|
|
4403
|
+
}
|
|
4404
|
+
return null;
|
|
4405
|
+
} catch {
|
|
4406
|
+
return null;
|
|
4541
4407
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4408
|
+
}
|
|
4409
|
+
function getBaseBranch(repoRoot) {
|
|
4410
|
+
const gitDir = resolveGitDir(repoRoot);
|
|
4411
|
+
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
4412
|
+
const candidates = ["main", "master", "develop", "trunk"];
|
|
4413
|
+
if (refStoreDir) {
|
|
4414
|
+
for (const candidate of candidates) {
|
|
4415
|
+
const refPath = path7.join(refStoreDir, "refs", "heads", candidate);
|
|
4416
|
+
if ((0, import_fs5.existsSync)(refPath)) {
|
|
4417
|
+
return candidate;
|
|
4418
|
+
}
|
|
4419
|
+
const packedRefs = readPackedRefs(refStoreDir);
|
|
4420
|
+
if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
|
|
4421
|
+
return candidate;
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4545
4424
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4425
|
+
return getCurrentBranch(repoRoot) ?? "main";
|
|
4426
|
+
}
|
|
4427
|
+
function getBranchOrDefault(repoRoot) {
|
|
4428
|
+
if (!isGitRepo(repoRoot)) {
|
|
4429
|
+
return "default";
|
|
4549
4430
|
}
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4431
|
+
return getCurrentBranch(repoRoot) ?? "default";
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
// src/config/paths.ts
|
|
4435
|
+
var import_fs6 = require("fs");
|
|
4436
|
+
var os3 = __toESM(require("os"), 1);
|
|
4437
|
+
var path8 = __toESM(require("path"), 1);
|
|
4438
|
+
var PROJECT_CONFIG_RELATIVE_PATH = path8.join(".opencode", "codebase-index.json");
|
|
4439
|
+
var PROJECT_INDEX_RELATIVE_PATH = path8.join(".opencode", "index");
|
|
4440
|
+
function resolveWorktreeFallbackPath(projectRoot, relativePath) {
|
|
4441
|
+
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
4442
|
+
if (!mainRepoRoot) {
|
|
4443
|
+
return null;
|
|
4554
4444
|
}
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4445
|
+
const fallbackPath = path8.join(mainRepoRoot, relativePath);
|
|
4446
|
+
return (0, import_fs6.existsSync)(fallbackPath) ? fallbackPath : null;
|
|
4447
|
+
}
|
|
4448
|
+
function hasProjectConfig(projectRoot) {
|
|
4449
|
+
return (0, import_fs6.existsSync)(path8.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
|
|
4450
|
+
}
|
|
4451
|
+
function getGlobalIndexPath() {
|
|
4452
|
+
return path8.join(os3.homedir(), ".opencode", "global-index");
|
|
4453
|
+
}
|
|
4454
|
+
function resolveProjectConfigPath(projectRoot) {
|
|
4455
|
+
const localConfigPath = path8.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
|
|
4456
|
+
if ((0, import_fs6.existsSync)(localConfigPath)) {
|
|
4457
|
+
return localConfigPath;
|
|
4559
4458
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4459
|
+
return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
|
|
4460
|
+
}
|
|
4461
|
+
function resolveProjectIndexPath(projectRoot, scope) {
|
|
4462
|
+
if (scope === "global") {
|
|
4463
|
+
return getGlobalIndexPath();
|
|
4564
4464
|
}
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
return this.inner.gcOrphanSymbols();
|
|
4465
|
+
const localIndexPath = path8.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
|
|
4466
|
+
if ((0, import_fs6.existsSync)(localIndexPath)) {
|
|
4467
|
+
return localIndexPath;
|
|
4569
4468
|
}
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
return this.inner.gcOrphanCallEdges();
|
|
4469
|
+
if (hasProjectConfig(projectRoot)) {
|
|
4470
|
+
return localIndexPath;
|
|
4573
4471
|
}
|
|
4574
|
-
|
|
4472
|
+
return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
|
|
4473
|
+
}
|
|
4575
4474
|
|
|
4576
4475
|
// src/indexer/index.ts
|
|
4577
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
|
|
4476
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
4578
4477
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
4579
4478
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
4580
4479
|
"function_declaration",
|
|
@@ -4601,7 +4500,15 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
|
4601
4500
|
"trigger_declaration",
|
|
4602
4501
|
"test_declaration",
|
|
4603
4502
|
"struct_declaration",
|
|
4604
|
-
"union_declaration"
|
|
4503
|
+
"union_declaration",
|
|
4504
|
+
// GDScript declarations whose names participate in the call graph.
|
|
4505
|
+
// `function_definition` and `class_definition` are already in the set
|
|
4506
|
+
// above (shared with Python/C/Bash and Python, respectively).
|
|
4507
|
+
"constructor_definition",
|
|
4508
|
+
"enum_definition",
|
|
4509
|
+
"signal_statement",
|
|
4510
|
+
"const_statement",
|
|
4511
|
+
"class_name_statement"
|
|
4605
4512
|
]);
|
|
4606
4513
|
function float32ArrayToBuffer(arr) {
|
|
4607
4514
|
const float32 = new Float32Array(arr);
|
|
@@ -4803,9 +4710,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
4803
4710
|
return true;
|
|
4804
4711
|
}
|
|
4805
4712
|
function isPathWithinRoot(filePath, rootPath) {
|
|
4806
|
-
const normalizedFilePath =
|
|
4807
|
-
const normalizedRoot =
|
|
4808
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
4713
|
+
const normalizedFilePath = path9.resolve(filePath);
|
|
4714
|
+
const normalizedRoot = path9.resolve(rootPath);
|
|
4715
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path9.sep}`);
|
|
4809
4716
|
}
|
|
4810
4717
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
4811
4718
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5858,22 +5765,28 @@ var Indexer = class {
|
|
|
5858
5765
|
this.projectRoot = projectRoot;
|
|
5859
5766
|
this.config = config;
|
|
5860
5767
|
this.indexPath = this.getIndexPath();
|
|
5861
|
-
this.fileHashCachePath =
|
|
5862
|
-
this.failedBatchesPath =
|
|
5863
|
-
this.indexingLockPath =
|
|
5768
|
+
this.fileHashCachePath = path9.join(this.indexPath, "file-hashes.json");
|
|
5769
|
+
this.failedBatchesPath = path9.join(this.indexPath, "failed-batches.json");
|
|
5770
|
+
this.indexingLockPath = path9.join(this.indexPath, "indexing.lock");
|
|
5864
5771
|
this.logger = initializeLogger(config.debug);
|
|
5865
5772
|
}
|
|
5866
5773
|
getIndexPath() {
|
|
5867
5774
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope);
|
|
5868
5775
|
}
|
|
5869
5776
|
loadFileHashCache() {
|
|
5777
|
+
if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
|
|
5778
|
+
return;
|
|
5779
|
+
}
|
|
5870
5780
|
try {
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5781
|
+
const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
|
|
5782
|
+
const parsed = JSON.parse(data);
|
|
5783
|
+
this.fileHashCache = new Map(Object.entries(parsed));
|
|
5784
|
+
} catch (error) {
|
|
5785
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5786
|
+
this.logger.warn("Failed to load file hash cache, resetting cache state", {
|
|
5787
|
+
fileHashCachePath: this.fileHashCachePath,
|
|
5788
|
+
error: message
|
|
5789
|
+
});
|
|
5877
5790
|
this.fileHashCache = /* @__PURE__ */ new Map();
|
|
5878
5791
|
}
|
|
5879
5792
|
}
|
|
@@ -5886,14 +5799,14 @@ var Indexer = class {
|
|
|
5886
5799
|
}
|
|
5887
5800
|
atomicWriteSync(targetPath, data) {
|
|
5888
5801
|
const tempPath = `${targetPath}.tmp`;
|
|
5889
|
-
(0, import_fs7.mkdirSync)(
|
|
5802
|
+
(0, import_fs7.mkdirSync)(path9.dirname(targetPath), { recursive: true });
|
|
5890
5803
|
(0, import_fs7.writeFileSync)(tempPath, data);
|
|
5891
5804
|
(0, import_fs7.renameSync)(tempPath, targetPath);
|
|
5892
5805
|
}
|
|
5893
5806
|
getScopedRoots() {
|
|
5894
|
-
const roots = /* @__PURE__ */ new Set([
|
|
5807
|
+
const roots = /* @__PURE__ */ new Set([path9.resolve(this.projectRoot)]);
|
|
5895
5808
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
5896
|
-
roots.add(
|
|
5809
|
+
roots.add(path9.resolve(this.projectRoot, kbRoot));
|
|
5897
5810
|
}
|
|
5898
5811
|
return Array.from(roots);
|
|
5899
5812
|
}
|
|
@@ -5902,22 +5815,22 @@ var Indexer = class {
|
|
|
5902
5815
|
if (this.config.scope !== "global") {
|
|
5903
5816
|
return branchName;
|
|
5904
5817
|
}
|
|
5905
|
-
const projectHash = hashContent(
|
|
5818
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
5906
5819
|
return `${projectHash}:${branchName}`;
|
|
5907
5820
|
}
|
|
5908
5821
|
getLegacyBranchCatalogKey() {
|
|
5909
5822
|
return this.currentBranch || "default";
|
|
5910
5823
|
}
|
|
5911
5824
|
getLegacyMigrationMetadataKey() {
|
|
5912
|
-
const projectHash = hashContent(
|
|
5825
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
5913
5826
|
return `index.globalBranchMigration.${projectHash}`;
|
|
5914
5827
|
}
|
|
5915
5828
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
5916
|
-
const projectHash = hashContent(
|
|
5829
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
5917
5830
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
5918
5831
|
}
|
|
5919
5832
|
getProjectForceReembedMetadataKey() {
|
|
5920
|
-
const projectHash = hashContent(
|
|
5833
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
5921
5834
|
return `index.forceReembed.${projectHash}`;
|
|
5922
5835
|
}
|
|
5923
5836
|
hasProjectForceReembedPending() {
|
|
@@ -6011,7 +5924,7 @@ var Indexer = class {
|
|
|
6011
5924
|
if (!this.database) {
|
|
6012
5925
|
return { chunkIds, symbolIds };
|
|
6013
5926
|
}
|
|
6014
|
-
const projectRootPath =
|
|
5927
|
+
const projectRootPath = path9.resolve(this.projectRoot);
|
|
6015
5928
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6016
5929
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6017
5930
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6034,7 +5947,7 @@ var Indexer = class {
|
|
|
6034
5947
|
if (this.config.scope !== "global") {
|
|
6035
5948
|
return this.getBranchCatalogCleanupKeys();
|
|
6036
5949
|
}
|
|
6037
|
-
const projectHash = hashContent(
|
|
5950
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
6038
5951
|
const keys = /* @__PURE__ */ new Set();
|
|
6039
5952
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6040
5953
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6118,7 +6031,7 @@ var Indexer = class {
|
|
|
6118
6031
|
if (!this.database || this.config.scope !== "global") {
|
|
6119
6032
|
return false;
|
|
6120
6033
|
}
|
|
6121
|
-
const projectHash = hashContent(
|
|
6034
|
+
const projectHash = hashContent(path9.resolve(this.projectRoot)).slice(0, 16);
|
|
6122
6035
|
const roots = this.getScopedRoots();
|
|
6123
6036
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6124
6037
|
return this.database.getAllBranches().some(
|
|
@@ -6152,7 +6065,7 @@ var Indexer = class {
|
|
|
6152
6065
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6153
6066
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6154
6067
|
]);
|
|
6155
|
-
const projectRootPath =
|
|
6068
|
+
const projectRootPath = path9.resolve(this.projectRoot);
|
|
6156
6069
|
const projectLocalFilePaths = new Set(
|
|
6157
6070
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6158
6071
|
);
|
|
@@ -6247,7 +6160,12 @@ var Indexer = class {
|
|
|
6247
6160
|
loadFailedBatches(maxChunkTokens) {
|
|
6248
6161
|
try {
|
|
6249
6162
|
return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
|
|
6250
|
-
} catch {
|
|
6163
|
+
} catch (error) {
|
|
6164
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6165
|
+
this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
|
|
6166
|
+
failedBatchesPath: this.failedBatchesPath,
|
|
6167
|
+
error: message
|
|
6168
|
+
});
|
|
6251
6169
|
return [];
|
|
6252
6170
|
}
|
|
6253
6171
|
}
|
|
@@ -6507,13 +6425,13 @@ var Indexer = class {
|
|
|
6507
6425
|
}
|
|
6508
6426
|
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
6509
6427
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6510
|
-
const storePath =
|
|
6428
|
+
const storePath = path9.join(this.indexPath, "vectors");
|
|
6511
6429
|
this.store = new VectorStore(storePath, dimensions);
|
|
6512
|
-
const indexFilePath =
|
|
6430
|
+
const indexFilePath = path9.join(this.indexPath, "vectors.usearch");
|
|
6513
6431
|
if ((0, import_fs7.existsSync)(indexFilePath)) {
|
|
6514
6432
|
this.store.load();
|
|
6515
6433
|
}
|
|
6516
|
-
const invertedIndexPath =
|
|
6434
|
+
const invertedIndexPath = path9.join(this.indexPath, "inverted-index.json");
|
|
6517
6435
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6518
6436
|
try {
|
|
6519
6437
|
this.invertedIndex.load();
|
|
@@ -6523,7 +6441,7 @@ var Indexer = class {
|
|
|
6523
6441
|
}
|
|
6524
6442
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6525
6443
|
}
|
|
6526
|
-
const dbPath =
|
|
6444
|
+
const dbPath = path9.join(this.indexPath, "codebase.db");
|
|
6527
6445
|
let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
|
|
6528
6446
|
try {
|
|
6529
6447
|
this.database = new Database(dbPath);
|
|
@@ -6604,7 +6522,7 @@ var Indexer = class {
|
|
|
6604
6522
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6605
6523
|
return {
|
|
6606
6524
|
resetCorruptedIndex: true,
|
|
6607
|
-
warning: this.getCorruptedIndexWarning(
|
|
6525
|
+
warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
|
|
6608
6526
|
};
|
|
6609
6527
|
}
|
|
6610
6528
|
throw error;
|
|
@@ -6619,7 +6537,7 @@ var Indexer = class {
|
|
|
6619
6537
|
return;
|
|
6620
6538
|
}
|
|
6621
6539
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6622
|
-
const storeBasePath =
|
|
6540
|
+
const storeBasePath = path9.join(this.indexPath, "vectors");
|
|
6623
6541
|
const storeIndexPath = `${storeBasePath}.usearch`;
|
|
6624
6542
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6625
6543
|
const backupIndexPath = `${storeIndexPath}.bak`;
|
|
@@ -6704,7 +6622,7 @@ var Indexer = class {
|
|
|
6704
6622
|
if (!isSqliteCorruptionError(error)) {
|
|
6705
6623
|
return false;
|
|
6706
6624
|
}
|
|
6707
|
-
const dbPath =
|
|
6625
|
+
const dbPath = path9.join(this.indexPath, "codebase.db");
|
|
6708
6626
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6709
6627
|
const errorMessage = getErrorMessage(error);
|
|
6710
6628
|
if (this.config.scope === "global") {
|
|
@@ -6727,15 +6645,15 @@ var Indexer = class {
|
|
|
6727
6645
|
this.indexCompatibility = null;
|
|
6728
6646
|
this.fileHashCache.clear();
|
|
6729
6647
|
const resetPaths = [
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6648
|
+
path9.join(this.indexPath, "codebase.db"),
|
|
6649
|
+
path9.join(this.indexPath, "codebase.db-shm"),
|
|
6650
|
+
path9.join(this.indexPath, "codebase.db-wal"),
|
|
6651
|
+
path9.join(this.indexPath, "vectors.usearch"),
|
|
6652
|
+
path9.join(this.indexPath, "inverted-index.json"),
|
|
6653
|
+
path9.join(this.indexPath, "file-hashes.json"),
|
|
6654
|
+
path9.join(this.indexPath, "failed-batches.json"),
|
|
6655
|
+
path9.join(this.indexPath, "indexing.lock"),
|
|
6656
|
+
path9.join(this.indexPath, "vectors")
|
|
6739
6657
|
];
|
|
6740
6658
|
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6741
6659
|
try {
|
|
@@ -7000,7 +6918,7 @@ var Indexer = class {
|
|
|
7000
6918
|
for (const parsed of parsedFiles) {
|
|
7001
6919
|
currentFilePaths.add(parsed.path);
|
|
7002
6920
|
if (parsed.chunks.length === 0) {
|
|
7003
|
-
const relativePath =
|
|
6921
|
+
const relativePath = path9.relative(this.projectRoot, parsed.path);
|
|
7004
6922
|
stats.parseFailures.push(relativePath);
|
|
7005
6923
|
}
|
|
7006
6924
|
let fileChunkCount = 0;
|
|
@@ -7283,7 +7201,7 @@ var Indexer = class {
|
|
|
7283
7201
|
for (const requestBatch of requestBatches) {
|
|
7284
7202
|
queue.add(async () => {
|
|
7285
7203
|
if (rateLimitBackoffMs > 0) {
|
|
7286
|
-
await new Promise((
|
|
7204
|
+
await new Promise((resolve10) => setTimeout(resolve10, rateLimitBackoffMs));
|
|
7287
7205
|
}
|
|
7288
7206
|
try {
|
|
7289
7207
|
const result = await pRetry(
|
|
@@ -7844,8 +7762,8 @@ var Indexer = class {
|
|
|
7844
7762
|
this.indexCompatibility = compatibility;
|
|
7845
7763
|
return;
|
|
7846
7764
|
}
|
|
7847
|
-
const localProjectIndexPath =
|
|
7848
|
-
if (
|
|
7765
|
+
const localProjectIndexPath = path9.join(this.projectRoot, ".opencode", "index");
|
|
7766
|
+
if (path9.resolve(this.indexPath) !== path9.resolve(localProjectIndexPath)) {
|
|
7849
7767
|
throw new Error(
|
|
7850
7768
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
7851
7769
|
);
|
|
@@ -7933,7 +7851,7 @@ var Indexer = class {
|
|
|
7933
7851
|
gcOrphanSymbols: 0,
|
|
7934
7852
|
gcOrphanCallEdges: 0,
|
|
7935
7853
|
resetCorruptedIndex: true,
|
|
7936
|
-
warning: this.getCorruptedIndexWarning(
|
|
7854
|
+
warning: this.getCorruptedIndexWarning(path9.join(this.indexPath, "codebase.db"))
|
|
7937
7855
|
};
|
|
7938
7856
|
}
|
|
7939
7857
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8379,7 +8297,7 @@ function percentile(values, p) {
|
|
|
8379
8297
|
return sorted[lowerIndex] + fraction * (sorted[upperIndex] - sorted[lowerIndex]);
|
|
8380
8298
|
}
|
|
8381
8299
|
function normalizePath(input) {
|
|
8382
|
-
return input
|
|
8300
|
+
return normalizePathSeparators(input);
|
|
8383
8301
|
}
|
|
8384
8302
|
function uniqueResultsByPath(results) {
|
|
8385
8303
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8542,53 +8460,257 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
8542
8460
|
};
|
|
8543
8461
|
}
|
|
8544
8462
|
|
|
8463
|
+
// src/eval/runner-config.ts
|
|
8464
|
+
var import_fs8 = require("fs");
|
|
8465
|
+
var os4 = __toESM(require("os"), 1);
|
|
8466
|
+
var path11 = __toESM(require("path"), 1);
|
|
8467
|
+
|
|
8468
|
+
// src/config/rebase.ts
|
|
8469
|
+
var path10 = __toESM(require("path"), 1);
|
|
8470
|
+
function isWithinRoot(rootDir, targetPath) {
|
|
8471
|
+
const relativePath = path10.relative(rootDir, targetPath);
|
|
8472
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
|
|
8473
|
+
}
|
|
8474
|
+
function rebasePathEntries(values, fromDir, toDir) {
|
|
8475
|
+
if (!Array.isArray(values)) {
|
|
8476
|
+
return [];
|
|
8477
|
+
}
|
|
8478
|
+
return values.filter((value) => typeof value === "string").map((value) => {
|
|
8479
|
+
const trimmed = value.trim();
|
|
8480
|
+
if (!trimmed || path10.isAbsolute(trimmed)) {
|
|
8481
|
+
return trimmed;
|
|
8482
|
+
}
|
|
8483
|
+
return normalizePathSeparators(path10.normalize(path10.relative(toDir, path10.resolve(fromDir, trimmed))));
|
|
8484
|
+
}).filter(Boolean);
|
|
8485
|
+
}
|
|
8486
|
+
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
8487
|
+
if (!Array.isArray(values)) {
|
|
8488
|
+
return [];
|
|
8489
|
+
}
|
|
8490
|
+
return values.filter((value) => typeof value === "string").map((value) => {
|
|
8491
|
+
const trimmed = value.trim();
|
|
8492
|
+
if (!trimmed) {
|
|
8493
|
+
return trimmed;
|
|
8494
|
+
}
|
|
8495
|
+
if (path10.isAbsolute(trimmed)) {
|
|
8496
|
+
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
8497
|
+
return normalizePathSeparators(path10.normalize(path10.relative(sourceRoot, trimmed) || "."));
|
|
8498
|
+
}
|
|
8499
|
+
return path10.normalize(trimmed);
|
|
8500
|
+
}
|
|
8501
|
+
const resolvedFromSource = path10.resolve(sourceRoot, trimmed);
|
|
8502
|
+
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
8503
|
+
return normalizePathSeparators(path10.normalize(trimmed));
|
|
8504
|
+
}
|
|
8505
|
+
return normalizePathSeparators(path10.normalize(path10.relative(targetRoot, resolvedFromSource)));
|
|
8506
|
+
}).filter(Boolean);
|
|
8507
|
+
}
|
|
8508
|
+
|
|
8509
|
+
// src/eval/runner-config.ts
|
|
8510
|
+
function isRecord(value) {
|
|
8511
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8512
|
+
}
|
|
8513
|
+
function isStringArray2(value) {
|
|
8514
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8515
|
+
}
|
|
8516
|
+
function validateEvalConfigShape(rawConfig, filePath) {
|
|
8517
|
+
if (!isRecord(rawConfig)) {
|
|
8518
|
+
throw new Error(`Eval config at ${filePath} must contain a JSON object at the root.`);
|
|
8519
|
+
}
|
|
8520
|
+
const config = rawConfig;
|
|
8521
|
+
if (config.knowledgeBases !== void 0 && !isStringArray2(config.knowledgeBases)) {
|
|
8522
|
+
throw new Error(`Eval config at ${filePath} field 'knowledgeBases' must be an array of strings.`);
|
|
8523
|
+
}
|
|
8524
|
+
if (config.additionalInclude !== void 0 && !isStringArray2(config.additionalInclude)) {
|
|
8525
|
+
throw new Error(`Eval config at ${filePath} field 'additionalInclude' must be an array of strings.`);
|
|
8526
|
+
}
|
|
8527
|
+
if (config.include !== void 0 && !isStringArray2(config.include)) {
|
|
8528
|
+
throw new Error(`Eval config at ${filePath} field 'include' must be an array of strings.`);
|
|
8529
|
+
}
|
|
8530
|
+
if (config.exclude !== void 0 && !isStringArray2(config.exclude)) {
|
|
8531
|
+
throw new Error(`Eval config at ${filePath} field 'exclude' must be an array of strings.`);
|
|
8532
|
+
}
|
|
8533
|
+
for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
|
|
8534
|
+
const value = config[section];
|
|
8535
|
+
if (value !== void 0 && !isRecord(value)) {
|
|
8536
|
+
throw new Error(`Eval config at ${filePath} field '${section}' must be an object.`);
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
return config;
|
|
8540
|
+
}
|
|
8541
|
+
function parseJsonConfigFile(filePath) {
|
|
8542
|
+
try {
|
|
8543
|
+
return validateEvalConfigShape(JSON.parse((0, import_fs8.readFileSync)(filePath, "utf-8")), filePath);
|
|
8544
|
+
} catch (error) {
|
|
8545
|
+
if (error instanceof Error && error.message.startsWith("Eval config at ")) {
|
|
8546
|
+
throw error;
|
|
8547
|
+
}
|
|
8548
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8549
|
+
throw new Error(`Failed to parse eval config JSON at ${filePath}: ${message}`);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
function toAbsolute(projectRoot, maybeRelative) {
|
|
8553
|
+
return path11.isAbsolute(maybeRelative) ? maybeRelative : path11.join(projectRoot, maybeRelative);
|
|
8554
|
+
}
|
|
8555
|
+
function isProjectScopedConfigPath(configPath) {
|
|
8556
|
+
return path11.basename(configPath) === "codebase-index.json" && path11.basename(path11.dirname(configPath)) === ".opencode";
|
|
8557
|
+
}
|
|
8558
|
+
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
8559
|
+
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
8560
|
+
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
8561
|
+
values,
|
|
8562
|
+
path11.dirname(path11.dirname(resolvedConfigPath)),
|
|
8563
|
+
projectRoot
|
|
8564
|
+
) : rebasePathEntries(
|
|
8565
|
+
values,
|
|
8566
|
+
path11.dirname(resolvedConfigPath),
|
|
8567
|
+
projectRoot
|
|
8568
|
+
);
|
|
8569
|
+
if (Array.isArray(config.knowledgeBases)) {
|
|
8570
|
+
config.knowledgeBases = rebaseEntries(config.knowledgeBases);
|
|
8571
|
+
}
|
|
8572
|
+
if (Array.isArray(config.additionalInclude)) {
|
|
8573
|
+
config.additionalInclude = rebaseEntries(config.additionalInclude);
|
|
8574
|
+
}
|
|
8575
|
+
return config;
|
|
8576
|
+
}
|
|
8577
|
+
function loadRawConfig(projectRoot, configPath) {
|
|
8578
|
+
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
8579
|
+
if (fromPath && (0, import_fs8.existsSync)(fromPath)) {
|
|
8580
|
+
return normalizeEvalConfigKnowledgeBases(
|
|
8581
|
+
parseJsonConfigFile(fromPath),
|
|
8582
|
+
projectRoot,
|
|
8583
|
+
fromPath
|
|
8584
|
+
);
|
|
8585
|
+
}
|
|
8586
|
+
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
8587
|
+
if ((0, import_fs8.existsSync)(projectConfig)) {
|
|
8588
|
+
return normalizeEvalConfigKnowledgeBases(
|
|
8589
|
+
parseJsonConfigFile(projectConfig),
|
|
8590
|
+
projectRoot,
|
|
8591
|
+
projectConfig
|
|
8592
|
+
);
|
|
8593
|
+
}
|
|
8594
|
+
const globalConfig = path11.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8595
|
+
if ((0, import_fs8.existsSync)(globalConfig)) {
|
|
8596
|
+
return parseJsonConfigFile(globalConfig);
|
|
8597
|
+
}
|
|
8598
|
+
return {};
|
|
8599
|
+
}
|
|
8600
|
+
function getIndexRootPath(projectRoot, scope) {
|
|
8601
|
+
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
8602
|
+
}
|
|
8603
|
+
function getLocalProjectIndexRoot(projectRoot) {
|
|
8604
|
+
return path11.join(projectRoot, ".opencode", "index");
|
|
8605
|
+
}
|
|
8606
|
+
function getLocalProjectConfigPath(projectRoot) {
|
|
8607
|
+
return path11.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8608
|
+
}
|
|
8609
|
+
function clearIndexRoot(projectRoot, scope) {
|
|
8610
|
+
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
8611
|
+
if ((0, import_fs8.existsSync)(indexRoot)) {
|
|
8612
|
+
(0, import_fs8.rmSync)(indexRoot, { recursive: true, force: true });
|
|
8613
|
+
}
|
|
8614
|
+
}
|
|
8615
|
+
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
8616
|
+
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
8617
|
+
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
8618
|
+
if (!configPath && (0, import_fs8.existsSync)(localConfigPath)) {
|
|
8619
|
+
return localConfigPath;
|
|
8620
|
+
}
|
|
8621
|
+
if (!(0, import_fs8.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
8622
|
+
return resolvedConfigPath;
|
|
8623
|
+
}
|
|
8624
|
+
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
8625
|
+
parseJsonConfigFile(resolvedConfigPath),
|
|
8626
|
+
projectRoot,
|
|
8627
|
+
resolvedConfigPath
|
|
8628
|
+
);
|
|
8629
|
+
(0, import_fs8.mkdirSync)(path11.dirname(localConfigPath), { recursive: true });
|
|
8630
|
+
(0, import_fs8.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
8631
|
+
return localConfigPath;
|
|
8632
|
+
}
|
|
8633
|
+
function loadParsedConfig(projectRoot, configPath) {
|
|
8634
|
+
const raw = loadRawConfig(projectRoot, configPath);
|
|
8635
|
+
return parseConfig(raw);
|
|
8636
|
+
}
|
|
8637
|
+
function resolveSearchConfig(parsedConfig, overrides) {
|
|
8638
|
+
const nextSearch = {
|
|
8639
|
+
...parsedConfig.search
|
|
8640
|
+
};
|
|
8641
|
+
if (overrides?.fusionStrategy !== void 0) {
|
|
8642
|
+
nextSearch.fusionStrategy = overrides.fusionStrategy;
|
|
8643
|
+
}
|
|
8644
|
+
if (overrides?.hybridWeight !== void 0) {
|
|
8645
|
+
nextSearch.hybridWeight = overrides.hybridWeight;
|
|
8646
|
+
}
|
|
8647
|
+
if (overrides?.rrfK !== void 0) {
|
|
8648
|
+
nextSearch.rrfK = overrides.rrfK;
|
|
8649
|
+
}
|
|
8650
|
+
if (overrides?.rerankTopN !== void 0) {
|
|
8651
|
+
nextSearch.rerankTopN = overrides.rerankTopN;
|
|
8652
|
+
}
|
|
8653
|
+
return {
|
|
8654
|
+
...parsedConfig,
|
|
8655
|
+
search: nextSearch
|
|
8656
|
+
};
|
|
8657
|
+
}
|
|
8658
|
+
function getEmbeddingCostPer1MTokens(embeddingProvider) {
|
|
8659
|
+
return embeddingProvider === "custom" || embeddingProvider === "auto" ? 0 : getDefaultModelForProvider(embeddingProvider).costPer1MTokens;
|
|
8660
|
+
}
|
|
8661
|
+
|
|
8545
8662
|
// src/eval/schema.ts
|
|
8546
|
-
var
|
|
8663
|
+
var import_fs9 = require("fs");
|
|
8547
8664
|
function parseJsonFile(filePath) {
|
|
8548
|
-
const content = (0,
|
|
8549
|
-
|
|
8665
|
+
const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
8666
|
+
try {
|
|
8667
|
+
return JSON.parse(content);
|
|
8668
|
+
} catch (error) {
|
|
8669
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8670
|
+
throw new Error(`Failed to parse JSON from ${filePath}: ${message}`);
|
|
8671
|
+
}
|
|
8550
8672
|
}
|
|
8551
|
-
function
|
|
8673
|
+
function isRecord2(value) {
|
|
8552
8674
|
return typeof value === "object" && value !== null;
|
|
8553
8675
|
}
|
|
8554
|
-
function
|
|
8676
|
+
function isStringArray3(value) {
|
|
8555
8677
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8556
8678
|
}
|
|
8557
|
-
function asPositiveNumber(value,
|
|
8679
|
+
function asPositiveNumber(value, path18) {
|
|
8558
8680
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
8559
|
-
throw new Error(`${
|
|
8681
|
+
throw new Error(`${path18} must be a non-negative number`);
|
|
8560
8682
|
}
|
|
8561
8683
|
return value;
|
|
8562
8684
|
}
|
|
8563
|
-
function parseQueryType(value,
|
|
8685
|
+
function parseQueryType(value, path18) {
|
|
8564
8686
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
8565
8687
|
return value;
|
|
8566
8688
|
}
|
|
8567
8689
|
throw new Error(
|
|
8568
|
-
`${
|
|
8690
|
+
`${path18} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
8569
8691
|
);
|
|
8570
8692
|
}
|
|
8571
|
-
function parseExpected(input,
|
|
8572
|
-
if (!
|
|
8573
|
-
throw new Error(`${
|
|
8693
|
+
function parseExpected(input, path18) {
|
|
8694
|
+
if (!isRecord2(input)) {
|
|
8695
|
+
throw new Error(`${path18} must be an object`);
|
|
8574
8696
|
}
|
|
8575
8697
|
const filePathRaw = input.filePath;
|
|
8576
8698
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
8577
8699
|
const symbolRaw = input.symbol;
|
|
8578
8700
|
const branchRaw = input.branch;
|
|
8579
8701
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
8580
|
-
const acceptableFiles =
|
|
8702
|
+
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
8581
8703
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
8582
|
-
throw new Error(`${
|
|
8704
|
+
throw new Error(`${path18} must include either expected.filePath or expected.acceptableFiles`);
|
|
8583
8705
|
}
|
|
8584
|
-
if (acceptableFilesRaw !== void 0 && !
|
|
8585
|
-
throw new Error(`${
|
|
8706
|
+
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
8707
|
+
throw new Error(`${path18}.acceptableFiles must be an array of strings`);
|
|
8586
8708
|
}
|
|
8587
8709
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
8588
|
-
throw new Error(`${
|
|
8710
|
+
throw new Error(`${path18}.symbol must be a string when provided`);
|
|
8589
8711
|
}
|
|
8590
8712
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
8591
|
-
throw new Error(`${
|
|
8713
|
+
throw new Error(`${path18}.branch must be a string when provided`);
|
|
8592
8714
|
}
|
|
8593
8715
|
return {
|
|
8594
8716
|
filePath,
|
|
@@ -8598,29 +8720,29 @@ function parseExpected(input, path13) {
|
|
|
8598
8720
|
};
|
|
8599
8721
|
}
|
|
8600
8722
|
function parseQuery(input, index) {
|
|
8601
|
-
const
|
|
8602
|
-
if (!
|
|
8603
|
-
throw new Error(`${
|
|
8723
|
+
const path18 = `queries[${index}]`;
|
|
8724
|
+
if (!isRecord2(input)) {
|
|
8725
|
+
throw new Error(`${path18} must be an object`);
|
|
8604
8726
|
}
|
|
8605
8727
|
const id = input.id;
|
|
8606
8728
|
const query = input.query;
|
|
8607
8729
|
const queryType = input.queryType;
|
|
8608
8730
|
const expected = input.expected;
|
|
8609
8731
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
8610
|
-
throw new Error(`${
|
|
8732
|
+
throw new Error(`${path18}.id must be a non-empty string`);
|
|
8611
8733
|
}
|
|
8612
8734
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
8613
|
-
throw new Error(`${
|
|
8735
|
+
throw new Error(`${path18}.query must be a non-empty string`);
|
|
8614
8736
|
}
|
|
8615
8737
|
return {
|
|
8616
8738
|
id,
|
|
8617
8739
|
query,
|
|
8618
|
-
queryType: parseQueryType(queryType, `${
|
|
8619
|
-
expected: parseExpected(expected, `${
|
|
8740
|
+
queryType: parseQueryType(queryType, `${path18}.queryType`),
|
|
8741
|
+
expected: parseExpected(expected, `${path18}.expected`)
|
|
8620
8742
|
};
|
|
8621
8743
|
}
|
|
8622
8744
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
8623
|
-
if (!
|
|
8745
|
+
if (!isRecord2(raw)) {
|
|
8624
8746
|
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
8625
8747
|
}
|
|
8626
8748
|
const version = raw.version;
|
|
@@ -8661,8 +8783,11 @@ function loadGoldenDataset(datasetPath) {
|
|
|
8661
8783
|
const parsed = parseJsonFile(datasetPath);
|
|
8662
8784
|
return parseGoldenDataset(parsed, datasetPath);
|
|
8663
8785
|
}
|
|
8786
|
+
function parseThresholdValue(value, fieldName, sourceLabel) {
|
|
8787
|
+
return value === void 0 ? void 0 : asPositiveNumber(value, `${sourceLabel}.thresholds.${fieldName}`);
|
|
8788
|
+
}
|
|
8664
8789
|
function parseBudget(raw, sourceLabel) {
|
|
8665
|
-
if (!
|
|
8790
|
+
if (!isRecord2(raw)) {
|
|
8666
8791
|
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
8667
8792
|
}
|
|
8668
8793
|
const name = raw.name;
|
|
@@ -8675,7 +8800,7 @@ function parseBudget(raw, sourceLabel) {
|
|
|
8675
8800
|
if (baselinePath !== void 0 && typeof baselinePath !== "string") {
|
|
8676
8801
|
throw new Error(`${sourceLabel}.baselinePath must be a string when provided`);
|
|
8677
8802
|
}
|
|
8678
|
-
if (!
|
|
8803
|
+
if (!isRecord2(thresholds)) {
|
|
8679
8804
|
throw new Error(`${sourceLabel}.thresholds must be an object`);
|
|
8680
8805
|
}
|
|
8681
8806
|
return {
|
|
@@ -8683,25 +8808,45 @@ function parseBudget(raw, sourceLabel) {
|
|
|
8683
8808
|
baselinePath: typeof baselinePath === "string" ? baselinePath : void 0,
|
|
8684
8809
|
failOnMissingBaseline: typeof failOnMissingBaseline === "boolean" ? failOnMissingBaseline : true,
|
|
8685
8810
|
thresholds: {
|
|
8686
|
-
hitAt5MaxDrop:
|
|
8687
|
-
|
|
8688
|
-
|
|
8811
|
+
hitAt5MaxDrop: parseThresholdValue(
|
|
8812
|
+
thresholds.hitAt5MaxDrop,
|
|
8813
|
+
"hitAt5MaxDrop",
|
|
8814
|
+
sourceLabel
|
|
8815
|
+
),
|
|
8816
|
+
mrrAt10MaxDrop: parseThresholdValue(
|
|
8817
|
+
thresholds.mrrAt10MaxDrop,
|
|
8818
|
+
"mrrAt10MaxDrop",
|
|
8819
|
+
sourceLabel
|
|
8820
|
+
),
|
|
8821
|
+
rawDistinctTop3RatioMaxDrop: parseThresholdValue(
|
|
8689
8822
|
thresholds.rawDistinctTop3RatioMaxDrop,
|
|
8690
|
-
|
|
8823
|
+
"rawDistinctTop3RatioMaxDrop",
|
|
8824
|
+
sourceLabel
|
|
8691
8825
|
),
|
|
8692
|
-
p95LatencyMaxMultiplier:
|
|
8826
|
+
p95LatencyMaxMultiplier: parseThresholdValue(
|
|
8693
8827
|
thresholds.p95LatencyMaxMultiplier,
|
|
8694
|
-
|
|
8828
|
+
"p95LatencyMaxMultiplier",
|
|
8829
|
+
sourceLabel
|
|
8695
8830
|
),
|
|
8696
|
-
p95LatencyMaxAbsoluteMs:
|
|
8831
|
+
p95LatencyMaxAbsoluteMs: parseThresholdValue(
|
|
8697
8832
|
thresholds.p95LatencyMaxAbsoluteMs,
|
|
8698
|
-
|
|
8833
|
+
"p95LatencyMaxAbsoluteMs",
|
|
8834
|
+
sourceLabel
|
|
8835
|
+
),
|
|
8836
|
+
minHitAt5: parseThresholdValue(
|
|
8837
|
+
thresholds.minHitAt5,
|
|
8838
|
+
"minHitAt5",
|
|
8839
|
+
sourceLabel
|
|
8840
|
+
),
|
|
8841
|
+
minMrrAt10: parseThresholdValue(
|
|
8842
|
+
thresholds.minMrrAt10,
|
|
8843
|
+
"minMrrAt10",
|
|
8844
|
+
sourceLabel
|
|
8699
8845
|
),
|
|
8700
|
-
|
|
8701
|
-
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`),
|
|
8702
|
-
minRawDistinctTop3Ratio: thresholds.minRawDistinctTop3Ratio === void 0 ? void 0 : asPositiveNumber(
|
|
8846
|
+
minRawDistinctTop3Ratio: parseThresholdValue(
|
|
8703
8847
|
thresholds.minRawDistinctTop3Ratio,
|
|
8704
|
-
|
|
8848
|
+
"minRawDistinctTop3Ratio",
|
|
8849
|
+
sourceLabel
|
|
8705
8850
|
)
|
|
8706
8851
|
}
|
|
8707
8852
|
};
|
|
@@ -8712,109 +8857,6 @@ function loadBudget(budgetPath) {
|
|
|
8712
8857
|
}
|
|
8713
8858
|
|
|
8714
8859
|
// src/eval/runner.ts
|
|
8715
|
-
function toAbsolute(projectRoot, maybeRelative) {
|
|
8716
|
-
return path9.isAbsolute(maybeRelative) ? maybeRelative : path9.join(projectRoot, maybeRelative);
|
|
8717
|
-
}
|
|
8718
|
-
function isProjectScopedConfigPath(configPath) {
|
|
8719
|
-
return path9.basename(configPath) === "codebase-index.json" && path9.basename(path9.dirname(configPath)) === ".opencode";
|
|
8720
|
-
}
|
|
8721
|
-
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
8722
|
-
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
8723
|
-
if (!Array.isArray(config.knowledgeBases)) {
|
|
8724
|
-
return config;
|
|
8725
|
-
}
|
|
8726
|
-
config.knowledgeBases = isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
8727
|
-
config.knowledgeBases,
|
|
8728
|
-
path9.dirname(path9.dirname(resolvedConfigPath)),
|
|
8729
|
-
projectRoot
|
|
8730
|
-
) : rebasePathEntries(
|
|
8731
|
-
config.knowledgeBases,
|
|
8732
|
-
path9.dirname(resolvedConfigPath),
|
|
8733
|
-
projectRoot
|
|
8734
|
-
);
|
|
8735
|
-
return config;
|
|
8736
|
-
}
|
|
8737
|
-
function loadRawConfig(projectRoot, configPath) {
|
|
8738
|
-
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
8739
|
-
if (fromPath && (0, import_fs9.existsSync)(fromPath)) {
|
|
8740
|
-
return normalizeEvalConfigKnowledgeBases(
|
|
8741
|
-
JSON.parse((0, import_fs11.readFileSync)(fromPath, "utf-8")),
|
|
8742
|
-
projectRoot,
|
|
8743
|
-
fromPath
|
|
8744
|
-
);
|
|
8745
|
-
}
|
|
8746
|
-
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
8747
|
-
if ((0, import_fs9.existsSync)(projectConfig)) {
|
|
8748
|
-
return normalizeEvalConfigKnowledgeBases(
|
|
8749
|
-
JSON.parse((0, import_fs11.readFileSync)(projectConfig, "utf-8")),
|
|
8750
|
-
projectRoot,
|
|
8751
|
-
projectConfig
|
|
8752
|
-
);
|
|
8753
|
-
}
|
|
8754
|
-
const globalConfig = path9.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8755
|
-
if ((0, import_fs9.existsSync)(globalConfig)) {
|
|
8756
|
-
return JSON.parse((0, import_fs11.readFileSync)(globalConfig, "utf-8"));
|
|
8757
|
-
}
|
|
8758
|
-
return {};
|
|
8759
|
-
}
|
|
8760
|
-
function getIndexRootPath(projectRoot, scope) {
|
|
8761
|
-
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
8762
|
-
}
|
|
8763
|
-
function getLocalProjectIndexRoot(projectRoot) {
|
|
8764
|
-
return path9.join(projectRoot, ".opencode", "index");
|
|
8765
|
-
}
|
|
8766
|
-
function getLocalProjectConfigPath(projectRoot) {
|
|
8767
|
-
return path9.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8768
|
-
}
|
|
8769
|
-
function clearIndexRoot(projectRoot, scope) {
|
|
8770
|
-
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
8771
|
-
if ((0, import_fs9.existsSync)(indexRoot)) {
|
|
8772
|
-
(0, import_fs12.rmSync)(indexRoot, { recursive: true, force: true });
|
|
8773
|
-
}
|
|
8774
|
-
}
|
|
8775
|
-
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
8776
|
-
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
8777
|
-
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
8778
|
-
if (!configPath && (0, import_fs9.existsSync)(localConfigPath)) {
|
|
8779
|
-
return localConfigPath;
|
|
8780
|
-
}
|
|
8781
|
-
if (!(0, import_fs9.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
8782
|
-
return resolvedConfigPath;
|
|
8783
|
-
}
|
|
8784
|
-
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
8785
|
-
JSON.parse((0, import_fs11.readFileSync)(resolvedConfigPath, "utf-8")),
|
|
8786
|
-
projectRoot,
|
|
8787
|
-
resolvedConfigPath
|
|
8788
|
-
);
|
|
8789
|
-
(0, import_fs10.mkdirSync)(path9.dirname(localConfigPath), { recursive: true });
|
|
8790
|
-
(0, import_fs13.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
8791
|
-
return localConfigPath;
|
|
8792
|
-
}
|
|
8793
|
-
function loadParsedConfig(projectRoot, configPath) {
|
|
8794
|
-
const raw = loadRawConfig(projectRoot, configPath);
|
|
8795
|
-
return parseConfig(raw);
|
|
8796
|
-
}
|
|
8797
|
-
function resolveSearchConfig(parsedConfig, overrides) {
|
|
8798
|
-
const nextSearch = {
|
|
8799
|
-
...parsedConfig.search
|
|
8800
|
-
};
|
|
8801
|
-
if (overrides?.fusionStrategy !== void 0) {
|
|
8802
|
-
nextSearch.fusionStrategy = overrides.fusionStrategy;
|
|
8803
|
-
}
|
|
8804
|
-
if (overrides?.hybridWeight !== void 0) {
|
|
8805
|
-
nextSearch.hybridWeight = overrides.hybridWeight;
|
|
8806
|
-
}
|
|
8807
|
-
if (overrides?.rrfK !== void 0) {
|
|
8808
|
-
nextSearch.rrfK = overrides.rrfK;
|
|
8809
|
-
}
|
|
8810
|
-
if (overrides?.rerankTopN !== void 0) {
|
|
8811
|
-
nextSearch.rerankTopN = overrides.rerankTopN;
|
|
8812
|
-
}
|
|
8813
|
-
return {
|
|
8814
|
-
...parsedConfig,
|
|
8815
|
-
search: nextSearch
|
|
8816
|
-
};
|
|
8817
|
-
}
|
|
8818
8860
|
async function runEvaluation(options) {
|
|
8819
8861
|
const datasetPath = toAbsolute(options.projectRoot, options.datasetPath);
|
|
8820
8862
|
const againstPath = options.againstPath ? toAbsolute(options.projectRoot, options.againstPath) : void 0;
|
|
@@ -8839,7 +8881,7 @@ async function runEvaluation(options) {
|
|
|
8839
8881
|
const start = import_perf_hooks2.performance.now();
|
|
8840
8882
|
const result = await indexer.search(query.query, 10, {
|
|
8841
8883
|
metadataOnly: true,
|
|
8842
|
-
filterByBranch: query.expected.branch
|
|
8884
|
+
filterByBranch: !!query.expected.branch
|
|
8843
8885
|
});
|
|
8844
8886
|
const elapsed = import_perf_hooks2.performance.now() - start;
|
|
8845
8887
|
const materialized = result.map((item) => ({
|
|
@@ -8854,7 +8896,7 @@ async function runEvaluation(options) {
|
|
|
8854
8896
|
}
|
|
8855
8897
|
const logger = indexer.getLogger();
|
|
8856
8898
|
const metricSnapshot = logger.getMetrics();
|
|
8857
|
-
const costPer1MTokensUsd =
|
|
8899
|
+
const costPer1MTokensUsd = getEmbeddingCostPer1MTokens(effectiveConfig.embeddingProvider);
|
|
8858
8900
|
const summary = {
|
|
8859
8901
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8860
8902
|
projectRoot: options.projectRoot,
|
|
@@ -8879,13 +8921,13 @@ async function runEvaluation(options) {
|
|
|
8879
8921
|
};
|
|
8880
8922
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
8881
8923
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
8882
|
-
writeJson(
|
|
8883
|
-
writeJson(
|
|
8924
|
+
writeJson(path12.join(outputDir, "summary.json"), summary);
|
|
8925
|
+
writeJson(path12.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
8884
8926
|
let comparison;
|
|
8885
8927
|
if (againstPath) {
|
|
8886
8928
|
const baseline = loadSummary(againstPath);
|
|
8887
8929
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
8888
|
-
writeJson(
|
|
8930
|
+
writeJson(path12.join(outputDir, "compare.json"), comparison);
|
|
8889
8931
|
}
|
|
8890
8932
|
let gate;
|
|
8891
8933
|
if (options.ciMode) {
|
|
@@ -8895,10 +8937,10 @@ async function runEvaluation(options) {
|
|
|
8895
8937
|
const budget = loadBudget(budgetPath);
|
|
8896
8938
|
if (!comparison && budget.baselinePath) {
|
|
8897
8939
|
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
8898
|
-
if ((0,
|
|
8940
|
+
if ((0, import_fs10.existsSync)(resolvedBaseline)) {
|
|
8899
8941
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
8900
8942
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
8901
|
-
writeJson(
|
|
8943
|
+
writeJson(path12.join(outputDir, "compare.json"), comparison);
|
|
8902
8944
|
} else if (budget.failOnMissingBaseline) {
|
|
8903
8945
|
throw new Error(
|
|
8904
8946
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -8908,7 +8950,7 @@ async function runEvaluation(options) {
|
|
|
8908
8950
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
8909
8951
|
}
|
|
8910
8952
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
8911
|
-
writeText(
|
|
8953
|
+
writeText(path12.join(outputDir, "summary.md"), markdown);
|
|
8912
8954
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
8913
8955
|
} finally {
|
|
8914
8956
|
await indexer.close();
|
|
@@ -8966,19 +9008,23 @@ async function runSweep(options, sweep) {
|
|
|
8966
9008
|
bestByMrrAt10,
|
|
8967
9009
|
bestByP95Latency
|
|
8968
9010
|
};
|
|
8969
|
-
writeJson(
|
|
9011
|
+
writeJson(path12.join(outputDir, "compare.json"), aggregate);
|
|
8970
9012
|
const md = createSummaryMarkdown(
|
|
8971
9013
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
8972
9014
|
bestByHitAt5?.comparison,
|
|
8973
9015
|
void 0,
|
|
8974
9016
|
aggregate
|
|
8975
9017
|
);
|
|
8976
|
-
writeText(
|
|
8977
|
-
writeJson(
|
|
9018
|
+
writeText(path12.join(outputDir, "summary.md"), md);
|
|
9019
|
+
writeJson(path12.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
8978
9020
|
return { outputDir, aggregate };
|
|
8979
9021
|
}
|
|
8980
9022
|
|
|
8981
9023
|
// src/eval/cli.ts
|
|
9024
|
+
var path14 = __toESM(require("path"), 1);
|
|
9025
|
+
|
|
9026
|
+
// src/eval/cli-parser.ts
|
|
9027
|
+
var path13 = __toESM(require("path"), 1);
|
|
8982
9028
|
function printUsage() {
|
|
8983
9029
|
console.log(`
|
|
8984
9030
|
Usage:
|
|
@@ -9050,12 +9096,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9050
9096
|
const arg = argv[i];
|
|
9051
9097
|
const next = argv[i + 1];
|
|
9052
9098
|
if (arg === "--project" && next) {
|
|
9053
|
-
parsed.projectRoot =
|
|
9099
|
+
parsed.projectRoot = path13.resolve(cwd, next);
|
|
9054
9100
|
i += 1;
|
|
9055
9101
|
continue;
|
|
9056
9102
|
}
|
|
9057
9103
|
if (arg === "--config" && next) {
|
|
9058
|
-
parsed.configPath =
|
|
9104
|
+
parsed.configPath = path13.resolve(cwd, next);
|
|
9059
9105
|
i += 1;
|
|
9060
9106
|
continue;
|
|
9061
9107
|
}
|
|
@@ -9174,6 +9220,8 @@ function toRunOptions(parsed) {
|
|
|
9174
9220
|
}
|
|
9175
9221
|
};
|
|
9176
9222
|
}
|
|
9223
|
+
|
|
9224
|
+
// src/eval/cli.ts
|
|
9177
9225
|
async function handleEvalCommand(args, cwd) {
|
|
9178
9226
|
const subcommand = args[0];
|
|
9179
9227
|
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
@@ -9211,72 +9259,326 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9211
9259
|
}
|
|
9212
9260
|
return 0;
|
|
9213
9261
|
}
|
|
9214
|
-
if (subcommand === "compare") {
|
|
9215
|
-
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
9216
|
-
if (!explicitAgainst) {
|
|
9217
|
-
throw new Error("eval compare requires --against <baseline summary.json>");
|
|
9218
|
-
}
|
|
9219
|
-
parsed.againstPath = explicitAgainst;
|
|
9220
|
-
const runOptions = toRunOptions(parsed);
|
|
9221
|
-
if (hasSweepOptions(parsed.sweep)) {
|
|
9222
|
-
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
9223
|
-
console.log(`Eval compare sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
9224
|
-
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
9225
|
-
console.error(
|
|
9226
|
-
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
9227
|
-
);
|
|
9228
|
-
return 1;
|
|
9229
|
-
}
|
|
9230
|
-
return 0;
|
|
9231
|
-
}
|
|
9232
|
-
const result = await runEvaluation(runOptions);
|
|
9233
|
-
console.log(`Eval compare complete. Artifacts: ${result.outputDir}`);
|
|
9234
|
-
return 0;
|
|
9262
|
+
if (subcommand === "compare") {
|
|
9263
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
9264
|
+
if (!explicitAgainst) {
|
|
9265
|
+
throw new Error("eval compare requires --against <baseline summary.json>");
|
|
9266
|
+
}
|
|
9267
|
+
parsed.againstPath = explicitAgainst;
|
|
9268
|
+
const runOptions = toRunOptions(parsed);
|
|
9269
|
+
if (hasSweepOptions(parsed.sweep)) {
|
|
9270
|
+
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
9271
|
+
console.log(`Eval compare sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
9272
|
+
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
9273
|
+
console.error(
|
|
9274
|
+
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
9275
|
+
);
|
|
9276
|
+
return 1;
|
|
9277
|
+
}
|
|
9278
|
+
return 0;
|
|
9279
|
+
}
|
|
9280
|
+
const result = await runEvaluation(runOptions);
|
|
9281
|
+
console.log(`Eval compare complete. Artifacts: ${result.outputDir}`);
|
|
9282
|
+
return 0;
|
|
9283
|
+
}
|
|
9284
|
+
if (subcommand === "diff") {
|
|
9285
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
9286
|
+
if (!explicitAgainst) {
|
|
9287
|
+
throw new Error("eval diff requires --against <baseline summary.json>");
|
|
9288
|
+
}
|
|
9289
|
+
if (!parsed.currentPath) {
|
|
9290
|
+
throw new Error("eval diff requires --current <current summary.json>");
|
|
9291
|
+
}
|
|
9292
|
+
parsed.againstPath = explicitAgainst;
|
|
9293
|
+
const currentPath = parsed.currentPath;
|
|
9294
|
+
if (!currentPath.endsWith(".json")) {
|
|
9295
|
+
throw new Error("eval diff --current must point to a summary JSON file");
|
|
9296
|
+
}
|
|
9297
|
+
if (!parsed.againstPath.endsWith(".json")) {
|
|
9298
|
+
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9299
|
+
}
|
|
9300
|
+
const currentSummary = loadSummary(path14.resolve(parsed.projectRoot, currentPath), {
|
|
9301
|
+
allowLegacyDiversityMetrics: true
|
|
9302
|
+
});
|
|
9303
|
+
const baselineSummary = loadSummary(path14.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9304
|
+
allowLegacyDiversityMetrics: true
|
|
9305
|
+
});
|
|
9306
|
+
const comparison = compareSummaries(
|
|
9307
|
+
currentSummary,
|
|
9308
|
+
baselineSummary,
|
|
9309
|
+
path14.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9310
|
+
);
|
|
9311
|
+
const outputDir = createRunDirectory(path14.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9312
|
+
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9313
|
+
writeJson(path14.join(outputDir, "compare.json"), comparison);
|
|
9314
|
+
writeText(path14.join(outputDir, "summary.md"), summaryMd);
|
|
9315
|
+
writeJson(path14.join(outputDir, "summary.json"), currentSummary);
|
|
9316
|
+
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9317
|
+
return 0;
|
|
9318
|
+
}
|
|
9319
|
+
throw new Error(`Unknown eval subcommand: ${subcommand}`);
|
|
9320
|
+
}
|
|
9321
|
+
|
|
9322
|
+
// src/mcp-server.ts
|
|
9323
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9324
|
+
var path16 = __toESM(require("path"), 1);
|
|
9325
|
+
var import_fs12 = require("fs");
|
|
9326
|
+
|
|
9327
|
+
// src/mcp-server/register-prompts.ts
|
|
9328
|
+
var import_zod = require("zod");
|
|
9329
|
+
function registerMcpPrompts(server) {
|
|
9330
|
+
server.prompt(
|
|
9331
|
+
"search",
|
|
9332
|
+
"Search codebase by meaning using semantic search",
|
|
9333
|
+
{ query: import_zod.z.string().describe("What to search for in the codebase") },
|
|
9334
|
+
(args) => ({
|
|
9335
|
+
messages: [{
|
|
9336
|
+
role: "user",
|
|
9337
|
+
content: {
|
|
9338
|
+
type: "text",
|
|
9339
|
+
text: `Search the codebase for: "${args.query}"
|
|
9340
|
+
|
|
9341
|
+
Use the codebase_search tool with this query. If you need just locations first, use codebase_peek instead to save tokens.`
|
|
9342
|
+
}
|
|
9343
|
+
}]
|
|
9344
|
+
})
|
|
9345
|
+
);
|
|
9346
|
+
server.prompt(
|
|
9347
|
+
"find",
|
|
9348
|
+
"Find code using hybrid approach (semantic + grep)",
|
|
9349
|
+
{ query: import_zod.z.string().describe("What to find in the codebase") },
|
|
9350
|
+
(args) => ({
|
|
9351
|
+
messages: [{
|
|
9352
|
+
role: "user",
|
|
9353
|
+
content: {
|
|
9354
|
+
type: "text",
|
|
9355
|
+
text: `Find code related to: "${args.query}"
|
|
9356
|
+
|
|
9357
|
+
Use a hybrid approach:
|
|
9358
|
+
1. First use codebase_peek to find semantic matches by meaning
|
|
9359
|
+
2. Then use grep for exact identifier matches
|
|
9360
|
+
3. Combine results for comprehensive coverage`
|
|
9361
|
+
}
|
|
9362
|
+
}]
|
|
9363
|
+
})
|
|
9364
|
+
);
|
|
9365
|
+
server.prompt(
|
|
9366
|
+
"index",
|
|
9367
|
+
"Index the codebase for semantic search",
|
|
9368
|
+
{ options: import_zod.z.string().optional().describe("Options: 'force' to rebuild, 'estimate' to check costs") },
|
|
9369
|
+
(args) => {
|
|
9370
|
+
const opts = args.options?.toLowerCase() ?? "";
|
|
9371
|
+
let instruction = "Use the index_codebase tool to index the codebase for semantic search.";
|
|
9372
|
+
if (opts.includes("force")) {
|
|
9373
|
+
instruction = "Use the index_codebase tool with force=true to rebuild the entire index from scratch.";
|
|
9374
|
+
} else if (opts.includes("estimate")) {
|
|
9375
|
+
instruction = "Use the index_codebase tool with estimateOnly=true to check the cost estimate before indexing.";
|
|
9376
|
+
}
|
|
9377
|
+
return {
|
|
9378
|
+
messages: [{
|
|
9379
|
+
role: "user",
|
|
9380
|
+
content: { type: "text", text: instruction }
|
|
9381
|
+
}]
|
|
9382
|
+
};
|
|
9383
|
+
}
|
|
9384
|
+
);
|
|
9385
|
+
server.prompt(
|
|
9386
|
+
"status",
|
|
9387
|
+
"Check if the codebase is indexed and ready",
|
|
9388
|
+
{},
|
|
9389
|
+
() => ({
|
|
9390
|
+
messages: [{
|
|
9391
|
+
role: "user",
|
|
9392
|
+
content: {
|
|
9393
|
+
type: "text",
|
|
9394
|
+
text: "Use the index_status tool to check if the codebase index is ready and show its current state."
|
|
9395
|
+
}
|
|
9396
|
+
}]
|
|
9397
|
+
})
|
|
9398
|
+
);
|
|
9399
|
+
server.prompt(
|
|
9400
|
+
"definition",
|
|
9401
|
+
"Find where a symbol is defined in the codebase",
|
|
9402
|
+
{ query: import_zod.z.string().describe("Symbol name or description to find the definition of") },
|
|
9403
|
+
(args) => ({
|
|
9404
|
+
messages: [{
|
|
9405
|
+
role: "user",
|
|
9406
|
+
content: {
|
|
9407
|
+
type: "text",
|
|
9408
|
+
text: `Find the definition of: "${args.query}"
|
|
9409
|
+
|
|
9410
|
+
Use the implementation_lookup tool to find where this symbol is defined. This prioritizes real implementation files over tests, docs, and examples. If no definition is found, fall back to codebase_search for broader discovery.`
|
|
9411
|
+
}
|
|
9412
|
+
}]
|
|
9413
|
+
})
|
|
9414
|
+
);
|
|
9415
|
+
}
|
|
9416
|
+
|
|
9417
|
+
// src/mcp-server/register-tools.ts
|
|
9418
|
+
var import_zod2 = require("zod");
|
|
9419
|
+
|
|
9420
|
+
// src/config/merger.ts
|
|
9421
|
+
var import_fs11 = require("fs");
|
|
9422
|
+
var os5 = __toESM(require("os"), 1);
|
|
9423
|
+
var path15 = __toESM(require("path"), 1);
|
|
9424
|
+
var PROJECT_OVERRIDE_KEYS = [
|
|
9425
|
+
"embeddingProvider",
|
|
9426
|
+
"customProvider",
|
|
9427
|
+
"embeddingModel",
|
|
9428
|
+
"reranker",
|
|
9429
|
+
"include",
|
|
9430
|
+
"exclude",
|
|
9431
|
+
"indexing",
|
|
9432
|
+
"search",
|
|
9433
|
+
"debug",
|
|
9434
|
+
"scope"
|
|
9435
|
+
];
|
|
9436
|
+
var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
|
|
9437
|
+
function isRecord3(value) {
|
|
9438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9439
|
+
}
|
|
9440
|
+
function isStringArray4(value) {
|
|
9441
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9442
|
+
}
|
|
9443
|
+
function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
|
|
9444
|
+
if (key in normalizedProjectConfig) {
|
|
9445
|
+
merged[key] = normalizedProjectConfig[key];
|
|
9446
|
+
return;
|
|
9447
|
+
}
|
|
9448
|
+
if (key in globalConfig) {
|
|
9449
|
+
merged[key] = globalConfig[key];
|
|
9235
9450
|
}
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9451
|
+
}
|
|
9452
|
+
function mergeUniqueStringArray(values) {
|
|
9453
|
+
return [...new Set(values.map((value) => String(value).trim()))];
|
|
9454
|
+
}
|
|
9455
|
+
function normalizeKnowledgeBasePath(value) {
|
|
9456
|
+
let normalized = path15.normalize(String(value).trim());
|
|
9457
|
+
const root = path15.parse(normalized).root;
|
|
9458
|
+
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
9459
|
+
normalized = normalized.slice(0, -1);
|
|
9460
|
+
}
|
|
9461
|
+
return normalized;
|
|
9462
|
+
}
|
|
9463
|
+
function mergeKnowledgeBasePaths(values) {
|
|
9464
|
+
return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
|
|
9465
|
+
}
|
|
9466
|
+
function validateConfigLayerShape(rawConfig, filePath) {
|
|
9467
|
+
if (!isRecord3(rawConfig)) {
|
|
9468
|
+
throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
|
|
9469
|
+
}
|
|
9470
|
+
if (rawConfig.knowledgeBases !== void 0 && !isStringArray4(rawConfig.knowledgeBases)) {
|
|
9471
|
+
throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
|
|
9472
|
+
}
|
|
9473
|
+
if (rawConfig.additionalInclude !== void 0 && !isStringArray4(rawConfig.additionalInclude)) {
|
|
9474
|
+
throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
|
|
9475
|
+
}
|
|
9476
|
+
if (rawConfig.include !== void 0 && !isStringArray4(rawConfig.include)) {
|
|
9477
|
+
throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
|
|
9478
|
+
}
|
|
9479
|
+
if (rawConfig.exclude !== void 0 && !isStringArray4(rawConfig.exclude)) {
|
|
9480
|
+
throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
|
|
9481
|
+
}
|
|
9482
|
+
for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
|
|
9483
|
+
const value = rawConfig[section];
|
|
9484
|
+
if (value !== void 0 && !isRecord3(value)) {
|
|
9485
|
+
throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
|
|
9240
9486
|
}
|
|
9241
|
-
|
|
9242
|
-
|
|
9487
|
+
}
|
|
9488
|
+
return rawConfig;
|
|
9489
|
+
}
|
|
9490
|
+
function loadJsonFile(filePath) {
|
|
9491
|
+
if (!(0, import_fs11.existsSync)(filePath)) {
|
|
9492
|
+
return null;
|
|
9493
|
+
}
|
|
9494
|
+
try {
|
|
9495
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
9496
|
+
return validateConfigLayerShape(JSON.parse(content), filePath);
|
|
9497
|
+
} catch (error) {
|
|
9498
|
+
if (error instanceof Error && error.message.startsWith("Config file ")) {
|
|
9499
|
+
throw error;
|
|
9243
9500
|
}
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9501
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9502
|
+
throw new Error(`Failed to load config file ${filePath}: ${message}`);
|
|
9503
|
+
}
|
|
9504
|
+
return null;
|
|
9505
|
+
}
|
|
9506
|
+
function materializeLocalProjectConfig(projectRoot, config) {
|
|
9507
|
+
const localConfigPath = path15.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9508
|
+
(0, import_fs11.mkdirSync)(path15.dirname(localConfigPath), { recursive: true });
|
|
9509
|
+
(0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
9510
|
+
return localConfigPath;
|
|
9511
|
+
}
|
|
9512
|
+
function loadProjectConfigLayer(projectRoot) {
|
|
9513
|
+
const projectConfigPath = resolveProjectConfigPath(projectRoot);
|
|
9514
|
+
const projectConfig = loadJsonFile(projectConfigPath);
|
|
9515
|
+
if (!projectConfig) {
|
|
9516
|
+
return {};
|
|
9517
|
+
}
|
|
9518
|
+
const normalizedConfig = { ...projectConfig };
|
|
9519
|
+
const projectConfigBaseDir = path15.dirname(path15.dirname(projectConfigPath));
|
|
9520
|
+
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
9521
|
+
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
9522
|
+
normalizedConfig.knowledgeBases,
|
|
9523
|
+
projectConfigBaseDir,
|
|
9524
|
+
projectRoot
|
|
9525
|
+
);
|
|
9526
|
+
}
|
|
9527
|
+
return normalizedConfig;
|
|
9528
|
+
}
|
|
9529
|
+
function loadMergedConfig(projectRoot) {
|
|
9530
|
+
const globalConfigPath = os5.homedir() + "/.config/opencode/codebase-index.json";
|
|
9531
|
+
const projectConfigPath = resolveProjectConfigPath(projectRoot);
|
|
9532
|
+
let globalConfig = null;
|
|
9533
|
+
let globalConfigError = null;
|
|
9534
|
+
try {
|
|
9535
|
+
globalConfig = loadJsonFile(globalConfigPath);
|
|
9536
|
+
} catch (error) {
|
|
9537
|
+
globalConfigError = error instanceof Error ? error : new Error(String(error));
|
|
9538
|
+
}
|
|
9539
|
+
const projectConfig = loadJsonFile(projectConfigPath);
|
|
9540
|
+
const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
|
|
9541
|
+
if (globalConfigError) {
|
|
9542
|
+
if (!projectConfig) {
|
|
9543
|
+
throw globalConfigError;
|
|
9248
9544
|
}
|
|
9249
|
-
|
|
9250
|
-
|
|
9545
|
+
globalConfig = null;
|
|
9546
|
+
}
|
|
9547
|
+
if (!globalConfig && !projectConfig) {
|
|
9548
|
+
return {};
|
|
9549
|
+
}
|
|
9550
|
+
if (!projectConfig && globalConfig) {
|
|
9551
|
+
return globalConfig;
|
|
9552
|
+
}
|
|
9553
|
+
if (!globalConfig && projectConfig) {
|
|
9554
|
+
return normalizedProjectConfig;
|
|
9555
|
+
}
|
|
9556
|
+
if (!globalConfig || !projectConfig) {
|
|
9557
|
+
return globalConfig ?? normalizedProjectConfig;
|
|
9558
|
+
}
|
|
9559
|
+
const merged = { ...globalConfig };
|
|
9560
|
+
for (const key of PROJECT_OVERRIDE_KEYS) {
|
|
9561
|
+
applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
|
|
9562
|
+
}
|
|
9563
|
+
if (projectConfig) {
|
|
9564
|
+
for (const key of Object.keys(projectConfig)) {
|
|
9565
|
+
if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
|
|
9566
|
+
continue;
|
|
9567
|
+
}
|
|
9568
|
+
merged[key] = normalizedProjectConfig[key];
|
|
9251
9569
|
}
|
|
9252
|
-
const currentSummary = loadSummary(path10.resolve(parsed.projectRoot, currentPath), {
|
|
9253
|
-
allowLegacyDiversityMetrics: true
|
|
9254
|
-
});
|
|
9255
|
-
const baselineSummary = loadSummary(path10.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9256
|
-
allowLegacyDiversityMetrics: true
|
|
9257
|
-
});
|
|
9258
|
-
const comparison = compareSummaries(
|
|
9259
|
-
currentSummary,
|
|
9260
|
-
baselineSummary,
|
|
9261
|
-
path10.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9262
|
-
);
|
|
9263
|
-
const outputDir = createRunDirectory(path10.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9264
|
-
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9265
|
-
writeJson(path10.join(outputDir, "compare.json"), comparison);
|
|
9266
|
-
writeText(path10.join(outputDir, "summary.md"), summaryMd);
|
|
9267
|
-
writeJson(path10.join(outputDir, "summary.json"), currentSummary);
|
|
9268
|
-
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9269
|
-
return 0;
|
|
9270
9570
|
}
|
|
9271
|
-
|
|
9571
|
+
const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
|
|
9572
|
+
const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
|
|
9573
|
+
const allKbs = [...globalKbs, ...projectKbs];
|
|
9574
|
+
merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
|
|
9575
|
+
const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
|
|
9576
|
+
const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
|
|
9577
|
+
const allAdditional = [...globalAdditional, ...projectAdditional];
|
|
9578
|
+
merged.additionalInclude = mergeUniqueStringArray(allAdditional);
|
|
9579
|
+
return merged;
|
|
9272
9580
|
}
|
|
9273
9581
|
|
|
9274
|
-
// src/mcp-server.ts
|
|
9275
|
-
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9276
|
-
var import_zod = require("zod");
|
|
9277
|
-
var path11 = __toESM(require("path"), 1);
|
|
9278
|
-
var import_fs14 = require("fs");
|
|
9279
|
-
|
|
9280
9582
|
// src/tools/utils.ts
|
|
9281
9583
|
var MAX_CONTENT_LINES = 30;
|
|
9282
9584
|
function truncateContent(content) {
|
|
@@ -9416,12 +9718,15 @@ function formatHealthCheck(result) {
|
|
|
9416
9718
|
}
|
|
9417
9719
|
return lines.join("\n");
|
|
9418
9720
|
}
|
|
9721
|
+
function formatResultHeader(result, index) {
|
|
9722
|
+
return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
|
|
9723
|
+
}
|
|
9419
9724
|
function formatDefinitionLookup(results, query) {
|
|
9420
9725
|
if (results.length === 0) {
|
|
9421
9726
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
9422
9727
|
}
|
|
9423
9728
|
const formatted = results.map((r, idx) => {
|
|
9424
|
-
const header = r
|
|
9729
|
+
const header = formatResultHeader(r, idx);
|
|
9425
9730
|
return `${header} (score: ${r.score.toFixed(2)})
|
|
9426
9731
|
\`\`\`
|
|
9427
9732
|
${truncateContent(r.content)}
|
|
@@ -9430,7 +9735,7 @@ ${truncateContent(r.content)}
|
|
|
9430
9735
|
return formatted.join("\n\n");
|
|
9431
9736
|
}
|
|
9432
9737
|
|
|
9433
|
-
// src/mcp-server.ts
|
|
9738
|
+
// src/mcp-server/shared.ts
|
|
9434
9739
|
var MAX_CONTENT_LINES2 = 30;
|
|
9435
9740
|
function truncateContent2(content) {
|
|
9436
9741
|
const lines = content.split("\n");
|
|
@@ -9451,50 +9756,23 @@ var CHUNK_TYPE_ENUM = [
|
|
|
9451
9756
|
"module",
|
|
9452
9757
|
"other"
|
|
9453
9758
|
];
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
version: "0.5.1"
|
|
9458
|
-
});
|
|
9459
|
-
const runtimeConfig = config;
|
|
9460
|
-
let indexer = new Indexer(projectRoot, runtimeConfig);
|
|
9461
|
-
let initialized = false;
|
|
9462
|
-
function refreshIndexerFromConfig() {
|
|
9463
|
-
indexer = new Indexer(projectRoot, runtimeConfig);
|
|
9464
|
-
initialized = false;
|
|
9465
|
-
}
|
|
9466
|
-
function shouldForceLocalizeProjectIndex() {
|
|
9467
|
-
if (runtimeConfig.scope !== "project") {
|
|
9468
|
-
return false;
|
|
9469
|
-
}
|
|
9470
|
-
const localIndexPath = path11.join(projectRoot, ".opencode", "index");
|
|
9471
|
-
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
9472
|
-
if (!mainRepoRoot) {
|
|
9473
|
-
return false;
|
|
9474
|
-
}
|
|
9475
|
-
const inheritedIndexPath = path11.join(mainRepoRoot, ".opencode", "index");
|
|
9476
|
-
return !(0, import_fs14.existsSync)(localIndexPath) && (0, import_fs14.existsSync)(inheritedIndexPath);
|
|
9477
|
-
}
|
|
9478
|
-
async function ensureInitialized() {
|
|
9479
|
-
if (!initialized) {
|
|
9480
|
-
await indexer.initialize();
|
|
9481
|
-
initialized = true;
|
|
9482
|
-
}
|
|
9483
|
-
}
|
|
9759
|
+
|
|
9760
|
+
// src/mcp-server/register-tools.ts
|
|
9761
|
+
function registerMcpTools(server, runtime) {
|
|
9484
9762
|
server.tool(
|
|
9485
9763
|
"codebase_search",
|
|
9486
9764
|
"Search codebase by MEANING, not keywords. Returns full code content. For just finding WHERE code is (saves ~90% tokens), use codebase_peek instead.",
|
|
9487
9765
|
{
|
|
9488
|
-
query:
|
|
9489
|
-
limit:
|
|
9490
|
-
fileType:
|
|
9491
|
-
directory:
|
|
9492
|
-
chunkType:
|
|
9493
|
-
contextLines:
|
|
9766
|
+
query: import_zod2.z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
|
|
9767
|
+
limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results to return"),
|
|
9768
|
+
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
9769
|
+
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
9770
|
+
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
9771
|
+
contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
|
|
9494
9772
|
},
|
|
9495
9773
|
async (args) => {
|
|
9496
|
-
await ensureInitialized();
|
|
9497
|
-
const results = await
|
|
9774
|
+
await runtime.ensureInitialized();
|
|
9775
|
+
const results = await runtime.getIndexer().search(args.query, args.limit ?? 5, {
|
|
9498
9776
|
fileType: args.fileType,
|
|
9499
9777
|
directory: args.directory,
|
|
9500
9778
|
chunkType: args.chunkType,
|
|
@@ -9519,15 +9797,15 @@ ${formatted.join("\n\n")}` }] };
|
|
|
9519
9797
|
"codebase_peek",
|
|
9520
9798
|
"Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Saves ~90% tokens vs codebase_search.",
|
|
9521
9799
|
{
|
|
9522
|
-
query:
|
|
9523
|
-
limit:
|
|
9524
|
-
fileType:
|
|
9525
|
-
directory:
|
|
9526
|
-
chunkType:
|
|
9800
|
+
query: import_zod2.z.string().describe("Natural language description of what code you're looking for."),
|
|
9801
|
+
limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
9802
|
+
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
9803
|
+
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
9804
|
+
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type")
|
|
9527
9805
|
},
|
|
9528
9806
|
async (args) => {
|
|
9529
|
-
await ensureInitialized();
|
|
9530
|
-
const results = await
|
|
9807
|
+
await runtime.ensureInitialized();
|
|
9808
|
+
const results = await runtime.getIndexer().search(args.query, args.limit ?? 10, {
|
|
9531
9809
|
fileType: args.fileType,
|
|
9532
9810
|
directory: args.directory,
|
|
9533
9811
|
chunkType: args.chunkType,
|
|
@@ -9552,29 +9830,29 @@ Use Read tool to examine specific files.` }] };
|
|
|
9552
9830
|
"index_codebase",
|
|
9553
9831
|
"Index the codebase for semantic search. Creates vector embeddings of code chunks. Incremental - only re-indexes changed files. Run before first codebase_search.",
|
|
9554
9832
|
{
|
|
9555
|
-
force:
|
|
9556
|
-
estimateOnly:
|
|
9557
|
-
verbose:
|
|
9833
|
+
force: import_zod2.z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
|
|
9834
|
+
estimateOnly: import_zod2.z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
|
|
9835
|
+
verbose: import_zod2.z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
|
|
9558
9836
|
},
|
|
9559
9837
|
async (args) => {
|
|
9560
9838
|
if (args.estimateOnly) {
|
|
9561
|
-
await ensureInitialized();
|
|
9562
|
-
const estimate = await
|
|
9839
|
+
await runtime.ensureInitialized();
|
|
9840
|
+
const estimate = await runtime.getIndexer().estimateCost();
|
|
9563
9841
|
return { content: [{ type: "text", text: formatCostEstimate(estimate) }] };
|
|
9564
9842
|
}
|
|
9565
9843
|
if (args.force) {
|
|
9566
|
-
if (shouldForceLocalizeProjectIndex()) {
|
|
9567
|
-
materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
|
|
9568
|
-
refreshIndexerFromConfig();
|
|
9844
|
+
if (runtime.shouldForceLocalizeProjectIndex()) {
|
|
9845
|
+
materializeLocalProjectConfig(runtime.projectRoot, loadProjectConfigLayer(runtime.projectRoot));
|
|
9846
|
+
runtime.refreshIndexerFromConfig();
|
|
9569
9847
|
}
|
|
9570
|
-
await ensureInitialized();
|
|
9571
|
-
await
|
|
9572
|
-
refreshIndexerFromConfig();
|
|
9573
|
-
await ensureInitialized();
|
|
9848
|
+
await runtime.ensureInitialized();
|
|
9849
|
+
await runtime.getIndexer().clearIndex();
|
|
9850
|
+
runtime.refreshIndexerFromConfig();
|
|
9851
|
+
await runtime.ensureInitialized();
|
|
9574
9852
|
} else {
|
|
9575
|
-
await ensureInitialized();
|
|
9853
|
+
await runtime.ensureInitialized();
|
|
9576
9854
|
}
|
|
9577
|
-
const stats = await
|
|
9855
|
+
const stats = await runtime.getIndexer().index();
|
|
9578
9856
|
return { content: [{ type: "text", text: formatIndexStats(stats, args.verbose ?? false) }] };
|
|
9579
9857
|
}
|
|
9580
9858
|
);
|
|
@@ -9583,8 +9861,8 @@ Use Read tool to examine specific files.` }] };
|
|
|
9583
9861
|
"Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
|
|
9584
9862
|
{},
|
|
9585
9863
|
async () => {
|
|
9586
|
-
await ensureInitialized();
|
|
9587
|
-
const status = await
|
|
9864
|
+
await runtime.ensureInitialized();
|
|
9865
|
+
const status = await runtime.getIndexer().getStatus();
|
|
9588
9866
|
return { content: [{ type: "text", text: formatStatus(status) }] };
|
|
9589
9867
|
}
|
|
9590
9868
|
);
|
|
@@ -9593,8 +9871,8 @@ Use Read tool to examine specific files.` }] };
|
|
|
9593
9871
|
"Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
9594
9872
|
{},
|
|
9595
9873
|
async () => {
|
|
9596
|
-
await ensureInitialized();
|
|
9597
|
-
const result = await
|
|
9874
|
+
await runtime.ensureInitialized();
|
|
9875
|
+
const result = await runtime.getIndexer().healthCheck();
|
|
9598
9876
|
return { content: [{ type: "text", text: formatHealthCheck(result) }] };
|
|
9599
9877
|
}
|
|
9600
9878
|
);
|
|
@@ -9603,8 +9881,8 @@ Use Read tool to examine specific files.` }] };
|
|
|
9603
9881
|
"Get metrics and performance statistics for the codebase index. Requires debug.enabled=true and debug.metrics=true in config.",
|
|
9604
9882
|
{},
|
|
9605
9883
|
async () => {
|
|
9606
|
-
await ensureInitialized();
|
|
9607
|
-
const logger =
|
|
9884
|
+
await runtime.ensureInitialized();
|
|
9885
|
+
const logger = runtime.getIndexer().getLogger();
|
|
9608
9886
|
if (!logger.isEnabled()) {
|
|
9609
9887
|
return { content: [{ type: "text", text: 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```' }] };
|
|
9610
9888
|
}
|
|
@@ -9618,13 +9896,13 @@ Use Read tool to examine specific files.` }] };
|
|
|
9618
9896
|
"index_logs",
|
|
9619
9897
|
"Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
|
|
9620
9898
|
{
|
|
9621
|
-
limit:
|
|
9622
|
-
category:
|
|
9623
|
-
level:
|
|
9899
|
+
limit: import_zod2.z.number().optional().default(20).describe("Maximum number of log entries to return"),
|
|
9900
|
+
category: import_zod2.z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
|
|
9901
|
+
level: import_zod2.z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
|
|
9624
9902
|
},
|
|
9625
9903
|
async (args) => {
|
|
9626
|
-
await ensureInitialized();
|
|
9627
|
-
const logger =
|
|
9904
|
+
await runtime.ensureInitialized();
|
|
9905
|
+
const logger = runtime.getIndexer().getLogger();
|
|
9628
9906
|
if (!logger.isEnabled()) {
|
|
9629
9907
|
return { content: [{ type: "text", text: 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```' }] };
|
|
9630
9908
|
}
|
|
@@ -9650,16 +9928,16 @@ Use Read tool to examine specific files.` }] };
|
|
|
9650
9928
|
"find_similar",
|
|
9651
9929
|
"Find code similar to a given snippet. Use for duplicate detection, pattern discovery, or refactoring prep.",
|
|
9652
9930
|
{
|
|
9653
|
-
code:
|
|
9654
|
-
limit:
|
|
9655
|
-
fileType:
|
|
9656
|
-
directory:
|
|
9657
|
-
chunkType:
|
|
9658
|
-
excludeFile:
|
|
9931
|
+
code: import_zod2.z.string().describe("The code snippet to find similar code for"),
|
|
9932
|
+
limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
9933
|
+
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
9934
|
+
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
9935
|
+
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
9936
|
+
excludeFile: import_zod2.z.string().optional().describe("Exclude results from this file path")
|
|
9659
9937
|
},
|
|
9660
9938
|
async (args) => {
|
|
9661
|
-
await ensureInitialized();
|
|
9662
|
-
const results = await
|
|
9939
|
+
await runtime.ensureInitialized();
|
|
9940
|
+
const results = await runtime.getIndexer().findSimilar(args.code, args.limit ?? 10, {
|
|
9663
9941
|
fileType: args.fileType,
|
|
9664
9942
|
directory: args.directory,
|
|
9665
9943
|
chunkType: args.chunkType,
|
|
@@ -9684,14 +9962,14 @@ ${formatted.join("\n\n")}` }] };
|
|
|
9684
9962
|
"implementation_lookup",
|
|
9685
9963
|
"Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s). Prefers real implementation files over tests, docs, examples, and fixtures.",
|
|
9686
9964
|
{
|
|
9687
|
-
query:
|
|
9688
|
-
limit:
|
|
9689
|
-
fileType:
|
|
9690
|
-
directory:
|
|
9965
|
+
query: import_zod2.z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
9966
|
+
limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results"),
|
|
9967
|
+
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
9968
|
+
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
9691
9969
|
},
|
|
9692
9970
|
async (args) => {
|
|
9693
|
-
await ensureInitialized();
|
|
9694
|
-
const results = await
|
|
9971
|
+
await runtime.ensureInitialized();
|
|
9972
|
+
const results = await runtime.getIndexer().search(args.query, args.limit ?? 5, {
|
|
9695
9973
|
fileType: args.fileType,
|
|
9696
9974
|
directory: args.directory,
|
|
9697
9975
|
definitionIntent: true
|
|
@@ -9703,12 +9981,13 @@ ${formatted.join("\n\n")}` }] };
|
|
|
9703
9981
|
"call_graph",
|
|
9704
9982
|
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
|
|
9705
9983
|
{
|
|
9706
|
-
name:
|
|
9707
|
-
direction:
|
|
9708
|
-
symbolId:
|
|
9984
|
+
name: import_zod2.z.string().describe("Function or method name to query"),
|
|
9985
|
+
direction: import_zod2.z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
9986
|
+
symbolId: import_zod2.z.string().optional().describe("Symbol ID (required for 'callees' direction)")
|
|
9709
9987
|
},
|
|
9710
9988
|
async (args) => {
|
|
9711
|
-
await ensureInitialized();
|
|
9989
|
+
await runtime.ensureInitialized();
|
|
9990
|
+
const indexer = runtime.getIndexer();
|
|
9712
9991
|
if (args.direction === "callees") {
|
|
9713
9992
|
if (!args.symbolId) {
|
|
9714
9993
|
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
@@ -9736,91 +10015,46 @@ ${formatted2.join("\n")}` }] };
|
|
|
9736
10015
|
${formatted.join("\n")}` }] };
|
|
9737
10016
|
}
|
|
9738
10017
|
);
|
|
9739
|
-
|
|
9740
|
-
"search",
|
|
9741
|
-
"Search codebase by meaning using semantic search",
|
|
9742
|
-
{ query: import_zod.z.string().describe("What to search for in the codebase") },
|
|
9743
|
-
(args) => ({
|
|
9744
|
-
messages: [{
|
|
9745
|
-
role: "user",
|
|
9746
|
-
content: {
|
|
9747
|
-
type: "text",
|
|
9748
|
-
text: `Search the codebase for: "${args.query}"
|
|
9749
|
-
|
|
9750
|
-
Use the codebase_search tool with this query. If you need just locations first, use codebase_peek instead to save tokens.`
|
|
9751
|
-
}
|
|
9752
|
-
}]
|
|
9753
|
-
})
|
|
9754
|
-
);
|
|
9755
|
-
server.prompt(
|
|
9756
|
-
"find",
|
|
9757
|
-
"Find code using hybrid approach (semantic + grep)",
|
|
9758
|
-
{ query: import_zod.z.string().describe("What to find in the codebase") },
|
|
9759
|
-
(args) => ({
|
|
9760
|
-
messages: [{
|
|
9761
|
-
role: "user",
|
|
9762
|
-
content: {
|
|
9763
|
-
type: "text",
|
|
9764
|
-
text: `Find code related to: "${args.query}"
|
|
10018
|
+
}
|
|
9765
10019
|
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
if (opts.includes("force")) {
|
|
9782
|
-
instruction = "Use the index_codebase tool with force=true to rebuild the entire index from scratch.";
|
|
9783
|
-
} else if (opts.includes("estimate")) {
|
|
9784
|
-
instruction = "Use the index_codebase tool with estimateOnly=true to check the cost estimate before indexing.";
|
|
9785
|
-
}
|
|
9786
|
-
return {
|
|
9787
|
-
messages: [{
|
|
9788
|
-
role: "user",
|
|
9789
|
-
content: { type: "text", text: instruction }
|
|
9790
|
-
}]
|
|
9791
|
-
};
|
|
10020
|
+
// src/mcp-server.ts
|
|
10021
|
+
function createMcpServer(projectRoot, config) {
|
|
10022
|
+
const server = new import_mcp.McpServer({
|
|
10023
|
+
name: "opencode-codebase-index",
|
|
10024
|
+
version: "0.5.1"
|
|
10025
|
+
});
|
|
10026
|
+
let indexer = new Indexer(projectRoot, config);
|
|
10027
|
+
let initialized = false;
|
|
10028
|
+
function refreshIndexerFromConfig() {
|
|
10029
|
+
indexer = new Indexer(projectRoot, config);
|
|
10030
|
+
initialized = false;
|
|
10031
|
+
}
|
|
10032
|
+
function shouldForceLocalizeProjectIndex() {
|
|
10033
|
+
if (config.scope !== "project") {
|
|
10034
|
+
return false;
|
|
9792
10035
|
}
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
(
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
content: {
|
|
9816
|
-
type: "text",
|
|
9817
|
-
text: `Find the definition of: "${args.query}"
|
|
9818
|
-
|
|
9819
|
-
Use the implementation_lookup tool to find where this symbol is defined. This prioritizes real implementation files over tests, docs, and examples. If no definition is found, fall back to codebase_search for broader discovery.`
|
|
9820
|
-
}
|
|
9821
|
-
}]
|
|
9822
|
-
})
|
|
9823
|
-
);
|
|
10036
|
+
const localIndexPath = path16.join(projectRoot, ".opencode", "index");
|
|
10037
|
+
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
10038
|
+
if (!mainRepoRoot) {
|
|
10039
|
+
return false;
|
|
10040
|
+
}
|
|
10041
|
+
const inheritedIndexPath = path16.join(mainRepoRoot, ".opencode", "index");
|
|
10042
|
+
return !(0, import_fs12.existsSync)(localIndexPath) && (0, import_fs12.existsSync)(inheritedIndexPath);
|
|
10043
|
+
}
|
|
10044
|
+
async function ensureInitialized() {
|
|
10045
|
+
if (!initialized) {
|
|
10046
|
+
await indexer.initialize();
|
|
10047
|
+
initialized = true;
|
|
10048
|
+
}
|
|
10049
|
+
}
|
|
10050
|
+
registerMcpTools(server, {
|
|
10051
|
+
projectRoot,
|
|
10052
|
+
ensureInitialized,
|
|
10053
|
+
getIndexer: () => indexer,
|
|
10054
|
+
refreshIndexerFromConfig,
|
|
10055
|
+
shouldForceLocalizeProjectIndex
|
|
10056
|
+
});
|
|
10057
|
+
registerMcpPrompts(server);
|
|
9824
10058
|
return server;
|
|
9825
10059
|
}
|
|
9826
10060
|
|
|
@@ -9830,9 +10064,9 @@ function parseArgs(argv) {
|
|
|
9830
10064
|
let config;
|
|
9831
10065
|
for (let i = 2; i < argv.length; i++) {
|
|
9832
10066
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
9833
|
-
project =
|
|
10067
|
+
project = path17.resolve(argv[++i]);
|
|
9834
10068
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
9835
|
-
config =
|
|
10069
|
+
config = path17.resolve(argv[++i]);
|
|
9836
10070
|
}
|
|
9837
10071
|
}
|
|
9838
10072
|
return { project, config };
|
|
@@ -9856,9 +10090,8 @@ async function main() {
|
|
|
9856
10090
|
process.on("SIGINT", shutdown);
|
|
9857
10091
|
process.on("SIGTERM", shutdown);
|
|
9858
10092
|
}
|
|
9859
|
-
main().catch((
|
|
9860
|
-
|
|
9861
|
-
console.error(`Fatal: ${message}`);
|
|
10093
|
+
main().catch((_error) => {
|
|
10094
|
+
console.error("Fatal: failed to start MCP server (check config and network)");
|
|
9862
10095
|
process.exit(1);
|
|
9863
10096
|
});
|
|
9864
10097
|
//# sourceMappingURL=cli.cjs.map
|