opencode-codebase-index 0.8.0 → 0.9.0

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