opencode-codebase-index 0.8.1 → 0.10.0

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