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