opencode-codebase-index 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -329,7 +329,7 @@ var require_ignore = __commonJS({
329
329
  // path matching.
330
330
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
331
331
  // @returns {TestResult} true if a file is ignored
332
- test(path12, checkUnignored, mode) {
332
+ test(path18, checkUnignored, mode) {
333
333
  let ignored = false;
334
334
  let unignored = false;
335
335
  let matchedRule;
@@ -338,7 +338,7 @@ var require_ignore = __commonJS({
338
338
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
339
339
  return;
340
340
  }
341
- const matched = rule[mode].test(path12);
341
+ const matched = rule[mode].test(path18);
342
342
  if (!matched) {
343
343
  return;
344
344
  }
@@ -359,17 +359,17 @@ var require_ignore = __commonJS({
359
359
  var throwError = (message, Ctor) => {
360
360
  throw new Ctor(message);
361
361
  };
362
- var checkPath = (path12, originalPath, doThrow) => {
363
- if (!isString(path12)) {
362
+ var checkPath = (path18, originalPath, doThrow) => {
363
+ if (!isString(path18)) {
364
364
  return doThrow(
365
365
  `path must be a string, but got \`${originalPath}\``,
366
366
  TypeError
367
367
  );
368
368
  }
369
- if (!path12) {
369
+ if (!path18) {
370
370
  return doThrow(`path must not be empty`, TypeError);
371
371
  }
372
- if (checkPath.isNotRelative(path12)) {
372
+ if (checkPath.isNotRelative(path18)) {
373
373
  const r = "`path.relative()`d";
374
374
  return doThrow(
375
375
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -378,7 +378,7 @@ var require_ignore = __commonJS({
378
378
  }
379
379
  return true;
380
380
  };
381
- var isNotRelative = (path12) => REGEX_TEST_INVALID_PATH.test(path12);
381
+ var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
382
382
  checkPath.isNotRelative = isNotRelative;
383
383
  checkPath.convert = (p) => p;
384
384
  var Ignore2 = class {
@@ -408,19 +408,19 @@ var require_ignore = __commonJS({
408
408
  }
409
409
  // @returns {TestResult}
410
410
  _test(originalPath, cache, checkUnignored, slices) {
411
- const path12 = originalPath && checkPath.convert(originalPath);
411
+ const path18 = originalPath && checkPath.convert(originalPath);
412
412
  checkPath(
413
- path12,
413
+ path18,
414
414
  originalPath,
415
415
  this._strictPathCheck ? throwError : RETURN_FALSE
416
416
  );
417
- return this._t(path12, cache, checkUnignored, slices);
417
+ return this._t(path18, cache, checkUnignored, slices);
418
418
  }
419
- checkIgnore(path12) {
420
- if (!REGEX_TEST_TRAILING_SLASH.test(path12)) {
421
- return this.test(path12);
419
+ checkIgnore(path18) {
420
+ if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
421
+ return this.test(path18);
422
422
  }
423
- const slices = path12.split(SLASH2).filter(Boolean);
423
+ const slices = path18.split(SLASH2).filter(Boolean);
424
424
  slices.pop();
425
425
  if (slices.length) {
426
426
  const parent = this._t(
@@ -433,18 +433,18 @@ var require_ignore = __commonJS({
433
433
  return parent;
434
434
  }
435
435
  }
436
- return this._rules.test(path12, false, MODE_CHECK_IGNORE);
436
+ return this._rules.test(path18, false, MODE_CHECK_IGNORE);
437
437
  }
438
- _t(path12, cache, checkUnignored, slices) {
439
- if (path12 in cache) {
440
- return cache[path12];
438
+ _t(path18, cache, checkUnignored, slices) {
439
+ if (path18 in cache) {
440
+ return cache[path18];
441
441
  }
442
442
  if (!slices) {
443
- slices = path12.split(SLASH2).filter(Boolean);
443
+ slices = path18.split(SLASH2).filter(Boolean);
444
444
  }
445
445
  slices.pop();
446
446
  if (!slices.length) {
447
- return cache[path12] = this._rules.test(path12, checkUnignored, MODE_IGNORE);
447
+ return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
448
448
  }
449
449
  const parent = this._t(
450
450
  slices.join(SLASH2) + SLASH2,
@@ -452,29 +452,29 @@ var require_ignore = __commonJS({
452
452
  checkUnignored,
453
453
  slices
454
454
  );
455
- return cache[path12] = parent.ignored ? parent : this._rules.test(path12, checkUnignored, MODE_IGNORE);
455
+ return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
456
456
  }
457
- ignores(path12) {
458
- return this._test(path12, this._ignoreCache, false).ignored;
457
+ ignores(path18) {
458
+ return this._test(path18, this._ignoreCache, false).ignored;
459
459
  }
460
460
  createFilter() {
461
- return (path12) => !this.ignores(path12);
461
+ return (path18) => !this.ignores(path18);
462
462
  }
463
463
  filter(paths) {
464
464
  return makeArray(paths).filter(this.createFilter());
465
465
  }
466
466
  // @returns {TestResult}
467
- test(path12) {
468
- return this._test(path12, this._testCache, true);
467
+ test(path18) {
468
+ return this._test(path18, this._testCache, true);
469
469
  }
470
470
  };
471
471
  var factory = (options) => new Ignore2(options);
472
- var isPathValid = (path12) => checkPath(path12 && checkPath.convert(path12), path12, RETURN_FALSE);
472
+ var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
473
473
  var setupWindows = () => {
474
474
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
475
475
  checkPath.convert = makePosix;
476
476
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
477
- checkPath.isNotRelative = (path12) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path12) || isNotRelative(path12);
477
+ checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
478
478
  };
479
479
  if (
480
480
  // Detect `process` so that it can run in browsers.
@@ -657,7 +657,8 @@ __export(index_exports, {
657
657
  default: () => index_default
658
658
  });
659
659
  module.exports = __toCommonJS(index_exports);
660
- var path11 = __toESM(require("path"), 1);
660
+ var os6 = __toESM(require("os"), 1);
661
+ var path17 = __toESM(require("path"), 1);
661
662
  var import_url2 = require("url");
662
663
 
663
664
  // src/config/constants.ts
@@ -674,7 +675,8 @@ var DEFAULT_INCLUDE = [
674
675
  "**/*.{md,mdx}",
675
676
  "**/*.{sh,bash,zsh}",
676
677
  "**/*.{txt,html,htm}",
677
- "**/*.zig"
678
+ "**/*.zig",
679
+ "**/*.gd"
678
680
  ];
679
681
  var DEFAULT_EXCLUDE = [
680
682
  "**/node_modules/**",
@@ -773,28 +775,7 @@ var AUTO_DETECT_PROVIDER_ORDER = [
773
775
  "google"
774
776
  ];
775
777
 
776
- // src/config/env-substitution.ts
777
- var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
778
- var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
779
- function substituteEnvString(value, keyPath) {
780
- const match = value.match(ENV_REFERENCE_PATTERN);
781
- if (!match) {
782
- if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
783
- throw new Error(
784
- `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
785
- );
786
- }
787
- return value;
788
- }
789
- const variableName = match[1];
790
- const envValue = process.env[variableName];
791
- if (envValue === void 0) {
792
- throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
793
- }
794
- return envValue;
795
- }
796
-
797
- // src/config/schema.ts
778
+ // src/config/defaults.ts
798
779
  function getDefaultIndexingConfig() {
799
780
  return {
800
781
  autoIndex: false,
@@ -823,15 +804,10 @@ function getDefaultSearchConfig() {
823
804
  rrfK: 60,
824
805
  rerankTopN: 20,
825
806
  contextLines: 0,
826
- routingHints: true
807
+ routingHints: true,
808
+ routingHintRole: "system"
827
809
  };
828
810
  }
829
- function isValidFusionStrategy(value) {
830
- return value === "weighted" || value === "rrf";
831
- }
832
- function isValidRerankerProvider(value) {
833
- return value === "cohere" || value === "jina" || value === "custom";
834
- }
835
811
  function getDefaultRerankerBaseUrl(provider) {
836
812
  switch (provider) {
837
813
  case "cohere":
@@ -854,8 +830,37 @@ function getDefaultDebugConfig() {
854
830
  metrics: true
855
831
  };
856
832
  }
833
+
834
+ // src/config/env-substitution.ts
835
+ var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
836
+ var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
837
+ function substituteEnvString(value, keyPath) {
838
+ const match = value.match(ENV_REFERENCE_PATTERN);
839
+ if (!match) {
840
+ if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
841
+ throw new Error(
842
+ `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
843
+ );
844
+ }
845
+ return value;
846
+ }
847
+ const variableName = match[1];
848
+ const envValue = process.env[variableName];
849
+ if (envValue === void 0) {
850
+ throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
851
+ }
852
+ return envValue;
853
+ }
854
+
855
+ // src/config/validators.ts
857
856
  var VALID_SCOPES = ["project", "global"];
858
857
  var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
858
+ function isValidFusionStrategy(value) {
859
+ return value === "weighted" || value === "rrf";
860
+ }
861
+ function isValidRerankerProvider(value) {
862
+ return value === "cohere" || value === "jina" || value === "custom";
863
+ }
859
864
  function isValidProvider(value) {
860
865
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
861
866
  }
@@ -883,6 +888,8 @@ function getResolvedStringArray(value, keyPath) {
883
888
  function isValidLogLevel(value) {
884
889
  return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
885
890
  }
891
+
892
+ // src/config/schema.ts
886
893
  function parseConfig(raw) {
887
894
  const input = raw && typeof raw === "object" ? raw : {};
888
895
  const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
@@ -919,7 +926,8 @@ function parseConfig(raw) {
919
926
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
920
927
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
921
928
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
922
- routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
929
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
930
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
923
931
  };
924
932
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
925
933
  const debug = {
@@ -937,9 +945,9 @@ function parseConfig(raw) {
937
945
  const rawAdditionalInclude = input.additionalInclude;
938
946
  const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
939
947
  let embeddingProvider;
940
- let embeddingModel = void 0;
941
- let customProvider = void 0;
942
- let reranker = void 0;
948
+ let embeddingModel;
949
+ let customProvider;
950
+ let reranker;
943
951
  if (embeddingProviderValue === "custom") {
944
952
  embeddingProvider = "custom";
945
953
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -972,7 +980,7 @@ function parseConfig(raw) {
972
980
  embeddingProvider = embeddingProviderValue;
973
981
  const rawEmbeddingModel = input.embeddingModel;
974
982
  if (typeof rawEmbeddingModel === "string") {
975
- const embeddingModelValue = substituteEnvString(rawEmbeddingModel, "$root.embeddingModel");
983
+ const embeddingModelValue = getResolvedString(rawEmbeddingModel, "$root.embeddingModel");
976
984
  if (embeddingModelValue) {
977
985
  embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
978
986
  }
@@ -1035,16 +1043,20 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1035
1043
  );
1036
1044
 
1037
1045
  // src/config/merger.ts
1038
- var import_fs3 = require("fs");
1046
+ var import_fs4 = require("fs");
1039
1047
  var os2 = __toESM(require("os"), 1);
1040
- var path3 = __toESM(require("path"), 1);
1048
+ var path6 = __toESM(require("path"), 1);
1041
1049
 
1042
1050
  // src/config/paths.ts
1043
- var import_fs2 = require("fs");
1051
+ var import_fs3 = require("fs");
1044
1052
  var os = __toESM(require("os"), 1);
1045
- var path2 = __toESM(require("path"), 1);
1053
+ var path3 = __toESM(require("path"), 1);
1046
1054
 
1047
1055
  // src/git/index.ts
1056
+ var import_fs2 = require("fs");
1057
+ var path2 = __toESM(require("path"), 1);
1058
+
1059
+ // src/git/refs.ts
1048
1060
  var import_fs = require("fs");
1049
1061
  var path = __toESM(require("path"), 1);
1050
1062
  function readPackedRefs(gitDir) {
@@ -1077,38 +1089,40 @@ function resolveCommonGitDir(gitDir) {
1077
1089
  }
1078
1090
  return gitDir;
1079
1091
  }
1092
+
1093
+ // src/git/index.ts
1080
1094
  function resolveWorktreeMainRepoRoot(repoRoot) {
1081
1095
  const gitDir = resolveGitDir(repoRoot);
1082
1096
  if (!gitDir) {
1083
1097
  return null;
1084
1098
  }
1085
1099
  const commonGitDir = resolveCommonGitDir(gitDir);
1086
- if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1100
+ if (commonGitDir === gitDir || path2.basename(commonGitDir) !== ".git") {
1087
1101
  return null;
1088
1102
  }
1089
- const mainRepoRoot = path.dirname(commonGitDir);
1090
- if (!(0, import_fs.existsSync)(mainRepoRoot)) {
1103
+ const mainRepoRoot = path2.dirname(commonGitDir);
1104
+ if (!(0, import_fs2.existsSync)(mainRepoRoot)) {
1091
1105
  return null;
1092
1106
  }
1093
- return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1107
+ return path2.resolve(mainRepoRoot) === path2.resolve(repoRoot) ? null : mainRepoRoot;
1094
1108
  }
1095
1109
  function resolveGitDir(repoRoot) {
1096
- const gitPath = path.join(repoRoot, ".git");
1097
- if (!(0, import_fs.existsSync)(gitPath)) {
1110
+ const gitPath = path2.join(repoRoot, ".git");
1111
+ if (!(0, import_fs2.existsSync)(gitPath)) {
1098
1112
  return null;
1099
1113
  }
1100
1114
  try {
1101
- const stat4 = (0, import_fs.statSync)(gitPath);
1115
+ const stat4 = (0, import_fs2.statSync)(gitPath);
1102
1116
  if (stat4.isDirectory()) {
1103
1117
  return gitPath;
1104
1118
  }
1105
1119
  if (stat4.isFile()) {
1106
- const content = (0, import_fs.readFileSync)(gitPath, "utf-8").trim();
1120
+ const content = (0, import_fs2.readFileSync)(gitPath, "utf-8").trim();
1107
1121
  const match = content.match(/^gitdir:\s*(.+)$/);
1108
1122
  if (match) {
1109
1123
  const gitdir = match[1];
1110
- const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1111
- if ((0, import_fs.existsSync)(resolvedPath)) {
1124
+ const resolvedPath = path2.isAbsolute(gitdir) ? gitdir : path2.resolve(repoRoot, gitdir);
1125
+ if ((0, import_fs2.existsSync)(resolvedPath)) {
1112
1126
  return resolvedPath;
1113
1127
  }
1114
1128
  }
@@ -1125,12 +1139,12 @@ function getCurrentBranch(repoRoot) {
1125
1139
  if (!gitDir) {
1126
1140
  return null;
1127
1141
  }
1128
- const headPath = path.join(gitDir, "HEAD");
1129
- if (!(0, import_fs.existsSync)(headPath)) {
1142
+ const headPath = path2.join(gitDir, "HEAD");
1143
+ if (!(0, import_fs2.existsSync)(headPath)) {
1130
1144
  return null;
1131
1145
  }
1132
1146
  try {
1133
- const headContent = (0, import_fs.readFileSync)(headPath, "utf-8").trim();
1147
+ const headContent = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
1134
1148
  const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1135
1149
  if (match) {
1136
1150
  return match[1];
@@ -1149,8 +1163,8 @@ function getBaseBranch(repoRoot) {
1149
1163
  const candidates = ["main", "master", "develop", "trunk"];
1150
1164
  if (refStoreDir) {
1151
1165
  for (const candidate of candidates) {
1152
- const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1153
- if ((0, import_fs.existsSync)(refPath)) {
1166
+ const refPath = path2.join(refStoreDir, "refs", "heads", candidate);
1167
+ if ((0, import_fs2.existsSync)(refPath)) {
1154
1168
  return candidate;
1155
1169
  }
1156
1170
  const packedRefs = readPackedRefs(refStoreDir);
@@ -1170,44 +1184,44 @@ function getBranchOrDefault(repoRoot) {
1170
1184
  function getHeadPath(repoRoot) {
1171
1185
  const gitDir = resolveGitDir(repoRoot);
1172
1186
  if (gitDir) {
1173
- return path.join(gitDir, "HEAD");
1187
+ return path2.join(gitDir, "HEAD");
1174
1188
  }
1175
- return path.join(repoRoot, ".git", "HEAD");
1189
+ return path2.join(repoRoot, ".git", "HEAD");
1176
1190
  }
1177
1191
 
1178
1192
  // src/config/paths.ts
1179
- var PROJECT_CONFIG_RELATIVE_PATH = path2.join(".opencode", "codebase-index.json");
1180
- var PROJECT_INDEX_RELATIVE_PATH = path2.join(".opencode", "index");
1193
+ var PROJECT_CONFIG_RELATIVE_PATH = path3.join(".opencode", "codebase-index.json");
1194
+ var PROJECT_INDEX_RELATIVE_PATH = path3.join(".opencode", "index");
1181
1195
  function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1182
1196
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1183
1197
  if (!mainRepoRoot) {
1184
1198
  return null;
1185
1199
  }
1186
- const fallbackPath = path2.join(mainRepoRoot, relativePath);
1187
- return (0, import_fs2.existsSync)(fallbackPath) ? fallbackPath : null;
1200
+ const fallbackPath = path3.join(mainRepoRoot, relativePath);
1201
+ return (0, import_fs3.existsSync)(fallbackPath) ? fallbackPath : null;
1188
1202
  }
1189
1203
  function hasProjectConfig(projectRoot) {
1190
- return (0, import_fs2.existsSync)(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1204
+ return (0, import_fs3.existsSync)(path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1191
1205
  }
1192
1206
  function getGlobalIndexPath() {
1193
- return path2.join(os.homedir(), ".opencode", "global-index");
1207
+ return path3.join(os.homedir(), ".opencode", "global-index");
1194
1208
  }
1195
1209
  function resolveProjectConfigPath(projectRoot) {
1196
- const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1197
- if ((0, import_fs2.existsSync)(localConfigPath)) {
1210
+ const localConfigPath = path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1211
+ if ((0, import_fs3.existsSync)(localConfigPath)) {
1198
1212
  return localConfigPath;
1199
1213
  }
1200
1214
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1201
1215
  }
1202
1216
  function resolveWritableProjectConfigPath(projectRoot) {
1203
- return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1217
+ return path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1204
1218
  }
1205
1219
  function resolveProjectIndexPath(projectRoot, scope) {
1206
1220
  if (scope === "global") {
1207
1221
  return getGlobalIndexPath();
1208
1222
  }
1209
- const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1210
- if ((0, import_fs2.existsSync)(localIndexPath)) {
1223
+ const localIndexPath = path3.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1224
+ if ((0, import_fs3.existsSync)(localIndexPath)) {
1211
1225
  return localIndexPath;
1212
1226
  }
1213
1227
  if (hasProjectConfig(projectRoot)) {
@@ -1216,23 +1230,56 @@ function resolveProjectIndexPath(projectRoot, scope) {
1216
1230
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1217
1231
  }
1218
1232
 
1219
- // src/config/merger.ts
1220
- function loadJsonFile(filePath) {
1221
- try {
1222
- if ((0, import_fs3.existsSync)(filePath)) {
1223
- const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
1224
- return JSON.parse(content);
1225
- }
1226
- } catch {
1227
- }
1228
- return null;
1233
+ // src/config/rebase.ts
1234
+ var path5 = __toESM(require("path"), 1);
1235
+
1236
+ // src/utils/paths.ts
1237
+ var path4 = __toESM(require("path"), 1);
1238
+ function normalizePathSeparators(value) {
1239
+ return value.replace(/\\/g, "/");
1229
1240
  }
1230
- function normalizeRelativeConfigPath(candidate) {
1231
- return candidate.replace(/\\/g, "/");
1241
+ function isHiddenPathSegment(part) {
1242
+ return part.startsWith(".") && part !== "." && part !== "..";
1232
1243
  }
1244
+ function isBuildPathSegment(part) {
1245
+ return part.toLowerCase().includes("build");
1246
+ }
1247
+ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1248
+ return relativePath.split(separator).some(
1249
+ (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1250
+ );
1251
+ }
1252
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1253
+ // macOS
1254
+ "library",
1255
+ "applications",
1256
+ "system",
1257
+ "volumes",
1258
+ "private",
1259
+ "cores",
1260
+ // Linux
1261
+ "proc",
1262
+ "sys",
1263
+ "dev",
1264
+ "run",
1265
+ "snap",
1266
+ // Windows
1267
+ "windows",
1268
+ "programdata",
1269
+ "program files",
1270
+ "program files (x86)",
1271
+ "$recycle.bin"
1272
+ ]);
1273
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1274
+ const firstSegment = relativePath.split(separator)[0];
1275
+ if (!firstSegment) return false;
1276
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1277
+ }
1278
+
1279
+ // src/config/rebase.ts
1233
1280
  function isWithinRoot(rootDir, targetPath) {
1234
- const relativePath = path3.relative(rootDir, targetPath);
1235
- return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1281
+ const relativePath = path5.relative(rootDir, targetPath);
1282
+ return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
1236
1283
  }
1237
1284
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1238
1285
  if (!Array.isArray(values)) {
@@ -1243,23 +1290,107 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1243
1290
  if (!trimmed) {
1244
1291
  return trimmed;
1245
1292
  }
1246
- if (path3.isAbsolute(trimmed)) {
1293
+ if (path5.isAbsolute(trimmed)) {
1247
1294
  if (isWithinRoot(sourceRoot, trimmed)) {
1248
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1295
+ return normalizePathSeparators(path5.normalize(path5.relative(sourceRoot, trimmed) || "."));
1249
1296
  }
1250
- return path3.normalize(trimmed);
1297
+ return path5.normalize(trimmed);
1251
1298
  }
1252
- const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1299
+ const resolvedFromSource = path5.resolve(sourceRoot, trimmed);
1253
1300
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1254
- return normalizeRelativeConfigPath(path3.normalize(trimmed));
1301
+ return normalizePathSeparators(path5.normalize(trimmed));
1255
1302
  }
1256
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1303
+ return normalizePathSeparators(path5.normalize(path5.relative(targetRoot, resolvedFromSource)));
1257
1304
  }).filter(Boolean);
1258
1305
  }
1306
+
1307
+ // src/config/merger.ts
1308
+ var PROJECT_OVERRIDE_KEYS = [
1309
+ "embeddingProvider",
1310
+ "customProvider",
1311
+ "embeddingModel",
1312
+ "reranker",
1313
+ "include",
1314
+ "exclude",
1315
+ "indexing",
1316
+ "search",
1317
+ "debug",
1318
+ "scope"
1319
+ ];
1320
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1321
+ function isRecord(value) {
1322
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1323
+ }
1324
+ function isStringArray2(value) {
1325
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1326
+ }
1327
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1328
+ if (key in normalizedProjectConfig) {
1329
+ merged[key] = normalizedProjectConfig[key];
1330
+ return;
1331
+ }
1332
+ if (key in globalConfig) {
1333
+ merged[key] = globalConfig[key];
1334
+ }
1335
+ }
1336
+ function mergeUniqueStringArray(values) {
1337
+ return [...new Set(values.map((value) => String(value).trim()))];
1338
+ }
1339
+ function normalizeKnowledgeBasePath(value) {
1340
+ let normalized = path6.normalize(String(value).trim());
1341
+ const root = path6.parse(normalized).root;
1342
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1343
+ normalized = normalized.slice(0, -1);
1344
+ }
1345
+ return normalized;
1346
+ }
1347
+ function mergeKnowledgeBasePaths(values) {
1348
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1349
+ }
1350
+ function validateConfigLayerShape(rawConfig, filePath) {
1351
+ if (!isRecord(rawConfig)) {
1352
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1353
+ }
1354
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1355
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1356
+ }
1357
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1358
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1359
+ }
1360
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1361
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1362
+ }
1363
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1364
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1365
+ }
1366
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1367
+ const value = rawConfig[section];
1368
+ if (value !== void 0 && !isRecord(value)) {
1369
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1370
+ }
1371
+ }
1372
+ return rawConfig;
1373
+ }
1374
+ function loadJsonFile(filePath) {
1375
+ if (!(0, import_fs4.existsSync)(filePath)) {
1376
+ return null;
1377
+ }
1378
+ try {
1379
+ const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
1380
+ return validateConfigLayerShape(JSON.parse(content), filePath);
1381
+ } catch (error) {
1382
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
1383
+ throw error;
1384
+ }
1385
+ const message = error instanceof Error ? error.message : String(error);
1386
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
1387
+ }
1388
+ return null;
1389
+ }
1259
1390
  function materializeLocalProjectConfig(projectRoot, config) {
1260
- const localConfigPath = path3.join(projectRoot, ".opencode", "codebase-index.json");
1261
- (0, import_fs3.mkdirSync)(path3.dirname(localConfigPath), { recursive: true });
1262
- (0, import_fs3.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1391
+ const localConfigPath = path6.join(projectRoot, ".opencode", "codebase-index.json");
1392
+ (0, import_fs4.mkdirSync)(path6.dirname(localConfigPath), { recursive: true });
1393
+ (0, import_fs4.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1263
1394
  return localConfigPath;
1264
1395
  }
1265
1396
  function loadProjectConfigLayer(projectRoot) {
@@ -1269,7 +1400,7 @@ function loadProjectConfigLayer(projectRoot) {
1269
1400
  return {};
1270
1401
  }
1271
1402
  const normalizedConfig = { ...projectConfig };
1272
- const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1403
+ const projectConfigBaseDir = path6.dirname(path6.dirname(projectConfigPath));
1273
1404
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
1274
1405
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1275
1406
  normalizedConfig.knowledgeBases,
@@ -1281,10 +1412,22 @@ function loadProjectConfigLayer(projectRoot) {
1281
1412
  }
1282
1413
  function loadMergedConfig(projectRoot) {
1283
1414
  const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1284
- const globalConfig = loadJsonFile(globalConfigPath);
1285
1415
  const projectConfigPath = resolveProjectConfigPath(projectRoot);
1416
+ let globalConfig = null;
1417
+ let globalConfigError = null;
1418
+ try {
1419
+ globalConfig = loadJsonFile(globalConfigPath);
1420
+ } catch (error) {
1421
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
1422
+ }
1286
1423
  const projectConfig = loadJsonFile(projectConfigPath);
1287
1424
  const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1425
+ if (globalConfigError) {
1426
+ if (!projectConfig) {
1427
+ throw globalConfigError;
1428
+ }
1429
+ globalConfig = null;
1430
+ }
1288
1431
  if (!globalConfig && !projectConfig) {
1289
1432
  return {};
1290
1433
  }
@@ -1294,60 +1437,16 @@ function loadMergedConfig(projectRoot) {
1294
1437
  if (!globalConfig && projectConfig) {
1295
1438
  return normalizedProjectConfig;
1296
1439
  }
1440
+ if (!globalConfig || !projectConfig) {
1441
+ return globalConfig ?? normalizedProjectConfig;
1442
+ }
1297
1443
  const merged = { ...globalConfig };
1298
- if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
1299
- merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
1300
- } else if (globalConfig && globalConfig.embeddingProvider) {
1301
- merged.embeddingProvider = globalConfig.embeddingProvider;
1302
- }
1303
- if (projectConfig && "customProvider" in normalizedProjectConfig) {
1304
- merged.customProvider = normalizedProjectConfig.customProvider;
1305
- } else if (globalConfig && globalConfig.customProvider) {
1306
- merged.customProvider = globalConfig.customProvider;
1307
- }
1308
- if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
1309
- merged.embeddingModel = normalizedProjectConfig.embeddingModel;
1310
- } else if (globalConfig && globalConfig.embeddingModel) {
1311
- merged.embeddingModel = globalConfig.embeddingModel;
1312
- }
1313
- if (projectConfig && "reranker" in normalizedProjectConfig) {
1314
- merged.reranker = normalizedProjectConfig.reranker;
1315
- } else if (globalConfig && globalConfig.reranker) {
1316
- merged.reranker = globalConfig.reranker;
1317
- }
1318
- if (projectConfig && "include" in normalizedProjectConfig) {
1319
- merged.include = normalizedProjectConfig.include;
1320
- } else if (globalConfig && globalConfig.include) {
1321
- merged.include = globalConfig.include;
1322
- }
1323
- if (projectConfig && "exclude" in normalizedProjectConfig) {
1324
- merged.exclude = normalizedProjectConfig.exclude;
1325
- } else if (globalConfig && globalConfig.exclude) {
1326
- merged.exclude = globalConfig.exclude;
1327
- }
1328
- if (projectConfig && "indexing" in normalizedProjectConfig) {
1329
- merged.indexing = normalizedProjectConfig.indexing;
1330
- } else if (globalConfig && globalConfig.indexing) {
1331
- merged.indexing = globalConfig.indexing;
1332
- }
1333
- if (projectConfig && "search" in normalizedProjectConfig) {
1334
- merged.search = normalizedProjectConfig.search;
1335
- } else if (globalConfig && globalConfig.search) {
1336
- merged.search = globalConfig.search;
1337
- }
1338
- if (projectConfig && "debug" in normalizedProjectConfig) {
1339
- merged.debug = normalizedProjectConfig.debug;
1340
- } else if (globalConfig && globalConfig.debug) {
1341
- merged.debug = globalConfig.debug;
1342
- }
1343
- if (projectConfig && "scope" in normalizedProjectConfig) {
1344
- merged.scope = normalizedProjectConfig.scope;
1345
- } else if (globalConfig && "scope" in globalConfig) {
1346
- merged.scope = globalConfig.scope;
1444
+ for (const key of PROJECT_OVERRIDE_KEYS) {
1445
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1347
1446
  }
1348
1447
  if (projectConfig) {
1349
1448
  for (const key of Object.keys(projectConfig)) {
1350
- 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") {
1449
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1351
1450
  continue;
1352
1451
  }
1353
1452
  merged[key] = normalizedProjectConfig[key];
@@ -1356,13 +1455,11 @@ function loadMergedConfig(projectRoot) {
1356
1455
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1357
1456
  const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1358
1457
  const allKbs = [...globalKbs, ...projectKbs];
1359
- const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1360
- merged.knowledgeBases = uniqueKbs;
1458
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1361
1459
  const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1362
1460
  const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1363
1461
  const allAdditional = [...globalAdditional, ...projectAdditional];
1364
- const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
1365
- merged.additionalInclude = uniqueAdditional;
1462
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1366
1463
  return merged;
1367
1464
  }
1368
1465
 
@@ -1456,7 +1553,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1456
1553
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1457
1554
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
1458
1555
  if (wantBigintFsStats) {
1459
- this._stat = (path12) => statMethod(path12, { bigint: true });
1556
+ this._stat = (path18) => statMethod(path18, { bigint: true });
1460
1557
  } else {
1461
1558
  this._stat = statMethod;
1462
1559
  }
@@ -1481,8 +1578,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1481
1578
  const par = this.parent;
1482
1579
  const fil = par && par.files;
1483
1580
  if (fil && fil.length > 0) {
1484
- const { path: path12, depth } = par;
1485
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1581
+ const { path: path18, depth } = par;
1582
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1486
1583
  const awaited = await Promise.all(slice);
1487
1584
  for (const entry of awaited) {
1488
1585
  if (!entry)
@@ -1522,20 +1619,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1522
1619
  this.reading = false;
1523
1620
  }
1524
1621
  }
1525
- async _exploreDir(path12, depth) {
1622
+ async _exploreDir(path18, depth) {
1526
1623
  let files;
1527
1624
  try {
1528
- files = await (0, import_promises.readdir)(path12, this._rdOptions);
1625
+ files = await (0, import_promises.readdir)(path18, this._rdOptions);
1529
1626
  } catch (error) {
1530
1627
  this._onError(error);
1531
1628
  }
1532
- return { files, depth, path: path12 };
1629
+ return { files, depth, path: path18 };
1533
1630
  }
1534
- async _formatEntry(dirent, path12) {
1631
+ async _formatEntry(dirent, path18) {
1535
1632
  let entry;
1536
1633
  const basename5 = this._isDirent ? dirent.name : dirent;
1537
1634
  try {
1538
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path12, basename5));
1635
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path18, basename5));
1539
1636
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
1540
1637
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1541
1638
  } catch (err) {
@@ -1935,16 +2032,16 @@ var delFromSet = (main, prop, item) => {
1935
2032
  };
1936
2033
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1937
2034
  var FsWatchInstances = /* @__PURE__ */ new Map();
1938
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
2035
+ function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
1939
2036
  const handleEvent = (rawEvent, evPath) => {
1940
- listener(path12);
1941
- emitRaw(rawEvent, evPath, { watchedPath: path12 });
1942
- if (evPath && path12 !== evPath) {
1943
- fsWatchBroadcast(sp.resolve(path12, evPath), KEY_LISTENERS, sp.join(path12, evPath));
2037
+ listener(path18);
2038
+ emitRaw(rawEvent, evPath, { watchedPath: path18 });
2039
+ if (evPath && path18 !== evPath) {
2040
+ fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
1944
2041
  }
1945
2042
  };
1946
2043
  try {
1947
- return (0, import_node_fs.watch)(path12, {
2044
+ return (0, import_node_fs.watch)(path18, {
1948
2045
  persistent: options.persistent
1949
2046
  }, handleEvent);
1950
2047
  } catch (error) {
@@ -1960,12 +2057,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1960
2057
  listener(val1, val2, val3);
1961
2058
  });
1962
2059
  };
1963
- var setFsWatchListener = (path12, fullPath, options, handlers) => {
2060
+ var setFsWatchListener = (path18, fullPath, options, handlers) => {
1964
2061
  const { listener, errHandler, rawEmitter } = handlers;
1965
2062
  let cont = FsWatchInstances.get(fullPath);
1966
2063
  let watcher;
1967
2064
  if (!options.persistent) {
1968
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
2065
+ watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
1969
2066
  if (!watcher)
1970
2067
  return;
1971
2068
  return watcher.close.bind(watcher);
@@ -1976,7 +2073,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1976
2073
  addAndConvert(cont, KEY_RAW, rawEmitter);
1977
2074
  } else {
1978
2075
  watcher = createFsWatchInstance(
1979
- path12,
2076
+ path18,
1980
2077
  options,
1981
2078
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1982
2079
  errHandler,
@@ -1991,7 +2088,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1991
2088
  cont.watcherUnusable = true;
1992
2089
  if (isWindows && error.code === "EPERM") {
1993
2090
  try {
1994
- const fd = await (0, import_promises2.open)(path12, "r");
2091
+ const fd = await (0, import_promises2.open)(path18, "r");
1995
2092
  await fd.close();
1996
2093
  broadcastErr(error);
1997
2094
  } catch (err) {
@@ -2022,7 +2119,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
2022
2119
  };
2023
2120
  };
2024
2121
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2025
- var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2122
+ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2026
2123
  const { listener, rawEmitter } = handlers;
2027
2124
  let cont = FsWatchFileInstances.get(fullPath);
2028
2125
  const copts = cont && cont.options;
@@ -2044,7 +2141,7 @@ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2044
2141
  });
2045
2142
  const currmtime = curr.mtimeMs;
2046
2143
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2047
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
2144
+ foreach(cont.listeners, (listener2) => listener2(path18, curr));
2048
2145
  }
2049
2146
  })
2050
2147
  };
@@ -2074,13 +2171,13 @@ var NodeFsHandler = class {
2074
2171
  * @param listener on fs change
2075
2172
  * @returns closer for the watcher instance
2076
2173
  */
2077
- _watchWithNodeFs(path12, listener) {
2174
+ _watchWithNodeFs(path18, listener) {
2078
2175
  const opts = this.fsw.options;
2079
- const directory = sp.dirname(path12);
2080
- const basename5 = sp.basename(path12);
2176
+ const directory = sp.dirname(path18);
2177
+ const basename5 = sp.basename(path18);
2081
2178
  const parent = this.fsw._getWatchedDir(directory);
2082
2179
  parent.add(basename5);
2083
- const absolutePath = sp.resolve(path12);
2180
+ const absolutePath = sp.resolve(path18);
2084
2181
  const options = {
2085
2182
  persistent: opts.persistent
2086
2183
  };
@@ -2090,12 +2187,12 @@ var NodeFsHandler = class {
2090
2187
  if (opts.usePolling) {
2091
2188
  const enableBin = opts.interval !== opts.binaryInterval;
2092
2189
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2093
- closer = setFsWatchFileListener(path12, absolutePath, options, {
2190
+ closer = setFsWatchFileListener(path18, absolutePath, options, {
2094
2191
  listener,
2095
2192
  rawEmitter: this.fsw._emitRaw
2096
2193
  });
2097
2194
  } else {
2098
- closer = setFsWatchListener(path12, absolutePath, options, {
2195
+ closer = setFsWatchListener(path18, absolutePath, options, {
2099
2196
  listener,
2100
2197
  errHandler: this._boundHandleError,
2101
2198
  rawEmitter: this.fsw._emitRaw
@@ -2117,7 +2214,7 @@ var NodeFsHandler = class {
2117
2214
  let prevStats = stats;
2118
2215
  if (parent.has(basename5))
2119
2216
  return;
2120
- const listener = async (path12, newStats) => {
2217
+ const listener = async (path18, newStats) => {
2121
2218
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2122
2219
  return;
2123
2220
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2131,11 +2228,11 @@ var NodeFsHandler = class {
2131
2228
  this.fsw._emit(EV.CHANGE, file, newStats2);
2132
2229
  }
2133
2230
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2134
- this.fsw._closeFile(path12);
2231
+ this.fsw._closeFile(path18);
2135
2232
  prevStats = newStats2;
2136
2233
  const closer2 = this._watchWithNodeFs(file, listener);
2137
2234
  if (closer2)
2138
- this.fsw._addPathCloser(path12, closer2);
2235
+ this.fsw._addPathCloser(path18, closer2);
2139
2236
  } else {
2140
2237
  prevStats = newStats2;
2141
2238
  }
@@ -2167,7 +2264,7 @@ var NodeFsHandler = class {
2167
2264
  * @param item basename of this item
2168
2265
  * @returns true if no more processing is needed for this entry.
2169
2266
  */
2170
- async _handleSymlink(entry, directory, path12, item) {
2267
+ async _handleSymlink(entry, directory, path18, item) {
2171
2268
  if (this.fsw.closed) {
2172
2269
  return;
2173
2270
  }
@@ -2177,7 +2274,7 @@ var NodeFsHandler = class {
2177
2274
  this.fsw._incrReadyCount();
2178
2275
  let linkPath;
2179
2276
  try {
2180
- linkPath = await (0, import_promises2.realpath)(path12);
2277
+ linkPath = await (0, import_promises2.realpath)(path18);
2181
2278
  } catch (e) {
2182
2279
  this.fsw._emitReady();
2183
2280
  return true;
@@ -2187,12 +2284,12 @@ var NodeFsHandler = class {
2187
2284
  if (dir.has(item)) {
2188
2285
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2189
2286
  this.fsw._symlinkPaths.set(full, linkPath);
2190
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
2287
+ this.fsw._emit(EV.CHANGE, path18, entry.stats);
2191
2288
  }
2192
2289
  } else {
2193
2290
  dir.add(item);
2194
2291
  this.fsw._symlinkPaths.set(full, linkPath);
2195
- this.fsw._emit(EV.ADD, path12, entry.stats);
2292
+ this.fsw._emit(EV.ADD, path18, entry.stats);
2196
2293
  }
2197
2294
  this.fsw._emitReady();
2198
2295
  return true;
@@ -2222,9 +2319,9 @@ var NodeFsHandler = class {
2222
2319
  return;
2223
2320
  }
2224
2321
  const item = entry.path;
2225
- let path12 = sp.join(directory, item);
2322
+ let path18 = sp.join(directory, item);
2226
2323
  current.add(item);
2227
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
2324
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2228
2325
  return;
2229
2326
  }
2230
2327
  if (this.fsw.closed) {
@@ -2233,11 +2330,11 @@ var NodeFsHandler = class {
2233
2330
  }
2234
2331
  if (item === target || !target && !previous.has(item)) {
2235
2332
  this.fsw._incrReadyCount();
2236
- path12 = sp.join(dir, sp.relative(dir, path12));
2237
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
2333
+ path18 = sp.join(dir, sp.relative(dir, path18));
2334
+ this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2238
2335
  }
2239
2336
  }).on(EV.ERROR, this._boundHandleError);
2240
- return new Promise((resolve9, reject) => {
2337
+ return new Promise((resolve12, reject) => {
2241
2338
  if (!stream)
2242
2339
  return reject();
2243
2340
  stream.once(STR_END, () => {
@@ -2246,7 +2343,7 @@ var NodeFsHandler = class {
2246
2343
  return;
2247
2344
  }
2248
2345
  const wasThrottled = throttler ? throttler.clear() : false;
2249
- resolve9(void 0);
2346
+ resolve12(void 0);
2250
2347
  previous.getChildren().filter((item) => {
2251
2348
  return item !== directory && !current.has(item);
2252
2349
  }).forEach((item) => {
@@ -2303,13 +2400,13 @@ var NodeFsHandler = class {
2303
2400
  * @param depth Child path actually targeted for watch
2304
2401
  * @param target Child path actually targeted for watch
2305
2402
  */
2306
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2403
+ async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2307
2404
  const ready = this.fsw._emitReady;
2308
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2405
+ if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2309
2406
  ready();
2310
2407
  return false;
2311
2408
  }
2312
- const wh = this.fsw._getWatchHelpers(path12);
2409
+ const wh = this.fsw._getWatchHelpers(path18);
2313
2410
  if (priorWh) {
2314
2411
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2315
2412
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2325,8 +2422,8 @@ var NodeFsHandler = class {
2325
2422
  const follow = this.fsw.options.followSymlinks;
2326
2423
  let closer;
2327
2424
  if (stats.isDirectory()) {
2328
- const absPath = sp.resolve(path12);
2329
- const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2425
+ const absPath = sp.resolve(path18);
2426
+ const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2330
2427
  if (this.fsw.closed)
2331
2428
  return;
2332
2429
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2336,29 +2433,29 @@ var NodeFsHandler = class {
2336
2433
  this.fsw._symlinkPaths.set(absPath, targetPath);
2337
2434
  }
2338
2435
  } else if (stats.isSymbolicLink()) {
2339
- const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2436
+ const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2340
2437
  if (this.fsw.closed)
2341
2438
  return;
2342
2439
  const parent = sp.dirname(wh.watchPath);
2343
2440
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2344
2441
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2345
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2442
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2346
2443
  if (this.fsw.closed)
2347
2444
  return;
2348
2445
  if (targetPath !== void 0) {
2349
- this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2446
+ this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2350
2447
  }
2351
2448
  } else {
2352
2449
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2353
2450
  }
2354
2451
  ready();
2355
2452
  if (closer)
2356
- this.fsw._addPathCloser(path12, closer);
2453
+ this.fsw._addPathCloser(path18, closer);
2357
2454
  return false;
2358
2455
  } catch (error) {
2359
2456
  if (this.fsw._handleError(error)) {
2360
2457
  ready();
2361
- return path12;
2458
+ return path18;
2362
2459
  }
2363
2460
  }
2364
2461
  }
@@ -2401,24 +2498,24 @@ function createPattern(matcher) {
2401
2498
  }
2402
2499
  return () => false;
2403
2500
  }
2404
- function normalizePath(path12) {
2405
- if (typeof path12 !== "string")
2501
+ function normalizePath(path18) {
2502
+ if (typeof path18 !== "string")
2406
2503
  throw new Error("string expected");
2407
- path12 = sp2.normalize(path12);
2408
- path12 = path12.replace(/\\/g, "/");
2504
+ path18 = sp2.normalize(path18);
2505
+ path18 = path18.replace(/\\/g, "/");
2409
2506
  let prepend = false;
2410
- if (path12.startsWith("//"))
2507
+ if (path18.startsWith("//"))
2411
2508
  prepend = true;
2412
- path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2509
+ path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2413
2510
  if (prepend)
2414
- path12 = "/" + path12;
2415
- return path12;
2511
+ path18 = "/" + path18;
2512
+ return path18;
2416
2513
  }
2417
2514
  function matchPatterns(patterns, testString, stats) {
2418
- const path12 = normalizePath(testString);
2515
+ const path18 = normalizePath(testString);
2419
2516
  for (let index = 0; index < patterns.length; index++) {
2420
2517
  const pattern = patterns[index];
2421
- if (pattern(path12, stats)) {
2518
+ if (pattern(path18, stats)) {
2422
2519
  return true;
2423
2520
  }
2424
2521
  }
@@ -2456,19 +2553,19 @@ var toUnix = (string) => {
2456
2553
  }
2457
2554
  return str;
2458
2555
  };
2459
- var normalizePathToUnix = (path12) => toUnix(sp2.normalize(toUnix(path12)));
2460
- var normalizeIgnored = (cwd = "") => (path12) => {
2461
- if (typeof path12 === "string") {
2462
- return normalizePathToUnix(sp2.isAbsolute(path12) ? path12 : sp2.join(cwd, path12));
2556
+ var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2557
+ var normalizeIgnored = (cwd = "") => (path18) => {
2558
+ if (typeof path18 === "string") {
2559
+ return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2463
2560
  } else {
2464
- return path12;
2561
+ return path18;
2465
2562
  }
2466
2563
  };
2467
- var getAbsolutePath = (path12, cwd) => {
2468
- if (sp2.isAbsolute(path12)) {
2469
- return path12;
2564
+ var getAbsolutePath = (path18, cwd) => {
2565
+ if (sp2.isAbsolute(path18)) {
2566
+ return path18;
2470
2567
  }
2471
- return sp2.join(cwd, path12);
2568
+ return sp2.join(cwd, path18);
2472
2569
  };
2473
2570
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2474
2571
  var DirEntry = class {
@@ -2533,10 +2630,10 @@ var WatchHelper = class {
2533
2630
  dirParts;
2534
2631
  followSymlinks;
2535
2632
  statMethod;
2536
- constructor(path12, follow, fsw) {
2633
+ constructor(path18, follow, fsw) {
2537
2634
  this.fsw = fsw;
2538
- const watchPath = path12;
2539
- this.path = path12 = path12.replace(REPLACER_RE, "");
2635
+ const watchPath = path18;
2636
+ this.path = path18 = path18.replace(REPLACER_RE, "");
2540
2637
  this.watchPath = watchPath;
2541
2638
  this.fullWatchPath = sp2.resolve(watchPath);
2542
2639
  this.dirParts = [];
@@ -2676,20 +2773,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2676
2773
  this._closePromise = void 0;
2677
2774
  let paths = unifyPaths(paths_);
2678
2775
  if (cwd) {
2679
- paths = paths.map((path12) => {
2680
- const absPath = getAbsolutePath(path12, cwd);
2776
+ paths = paths.map((path18) => {
2777
+ const absPath = getAbsolutePath(path18, cwd);
2681
2778
  return absPath;
2682
2779
  });
2683
2780
  }
2684
- paths.forEach((path12) => {
2685
- this._removeIgnoredPath(path12);
2781
+ paths.forEach((path18) => {
2782
+ this._removeIgnoredPath(path18);
2686
2783
  });
2687
2784
  this._userIgnored = void 0;
2688
2785
  if (!this._readyCount)
2689
2786
  this._readyCount = 0;
2690
2787
  this._readyCount += paths.length;
2691
- Promise.all(paths.map(async (path12) => {
2692
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2788
+ Promise.all(paths.map(async (path18) => {
2789
+ const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2693
2790
  if (res)
2694
2791
  this._emitReady();
2695
2792
  return res;
@@ -2711,17 +2808,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2711
2808
  return this;
2712
2809
  const paths = unifyPaths(paths_);
2713
2810
  const { cwd } = this.options;
2714
- paths.forEach((path12) => {
2715
- if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2811
+ paths.forEach((path18) => {
2812
+ if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2716
2813
  if (cwd)
2717
- path12 = sp2.join(cwd, path12);
2718
- path12 = sp2.resolve(path12);
2814
+ path18 = sp2.join(cwd, path18);
2815
+ path18 = sp2.resolve(path18);
2719
2816
  }
2720
- this._closePath(path12);
2721
- this._addIgnoredPath(path12);
2722
- if (this._watched.has(path12)) {
2817
+ this._closePath(path18);
2818
+ this._addIgnoredPath(path18);
2819
+ if (this._watched.has(path18)) {
2723
2820
  this._addIgnoredPath({
2724
- path: path12,
2821
+ path: path18,
2725
2822
  recursive: true
2726
2823
  });
2727
2824
  }
@@ -2785,38 +2882,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2785
2882
  * @param stats arguments to be passed with event
2786
2883
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2787
2884
  */
2788
- async _emit(event, path12, stats) {
2885
+ async _emit(event, path18, stats) {
2789
2886
  if (this.closed)
2790
2887
  return;
2791
2888
  const opts = this.options;
2792
2889
  if (isWindows)
2793
- path12 = sp2.normalize(path12);
2890
+ path18 = sp2.normalize(path18);
2794
2891
  if (opts.cwd)
2795
- path12 = sp2.relative(opts.cwd, path12);
2796
- const args = [path12];
2892
+ path18 = sp2.relative(opts.cwd, path18);
2893
+ const args = [path18];
2797
2894
  if (stats != null)
2798
2895
  args.push(stats);
2799
2896
  const awf = opts.awaitWriteFinish;
2800
2897
  let pw;
2801
- if (awf && (pw = this._pendingWrites.get(path12))) {
2898
+ if (awf && (pw = this._pendingWrites.get(path18))) {
2802
2899
  pw.lastChange = /* @__PURE__ */ new Date();
2803
2900
  return this;
2804
2901
  }
2805
2902
  if (opts.atomic) {
2806
2903
  if (event === EVENTS.UNLINK) {
2807
- this._pendingUnlinks.set(path12, [event, ...args]);
2904
+ this._pendingUnlinks.set(path18, [event, ...args]);
2808
2905
  setTimeout(() => {
2809
- this._pendingUnlinks.forEach((entry, path13) => {
2906
+ this._pendingUnlinks.forEach((entry, path19) => {
2810
2907
  this.emit(...entry);
2811
2908
  this.emit(EVENTS.ALL, ...entry);
2812
- this._pendingUnlinks.delete(path13);
2909
+ this._pendingUnlinks.delete(path19);
2813
2910
  });
2814
2911
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2815
2912
  return this;
2816
2913
  }
2817
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2914
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2818
2915
  event = EVENTS.CHANGE;
2819
- this._pendingUnlinks.delete(path12);
2916
+ this._pendingUnlinks.delete(path18);
2820
2917
  }
2821
2918
  }
2822
2919
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2834,16 +2931,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2834
2931
  this.emitWithAll(event, args);
2835
2932
  }
2836
2933
  };
2837
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2934
+ this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2838
2935
  return this;
2839
2936
  }
2840
2937
  if (event === EVENTS.CHANGE) {
2841
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2938
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2842
2939
  if (isThrottled)
2843
2940
  return this;
2844
2941
  }
2845
2942
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2846
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path12) : path12;
2943
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2847
2944
  let stats2;
2848
2945
  try {
2849
2946
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -2874,23 +2971,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2874
2971
  * @param timeout duration of time to suppress duplicate actions
2875
2972
  * @returns tracking object or false if action should be suppressed
2876
2973
  */
2877
- _throttle(actionType, path12, timeout) {
2974
+ _throttle(actionType, path18, timeout) {
2878
2975
  if (!this._throttled.has(actionType)) {
2879
2976
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2880
2977
  }
2881
2978
  const action = this._throttled.get(actionType);
2882
2979
  if (!action)
2883
2980
  throw new Error("invalid throttle");
2884
- const actionPath = action.get(path12);
2981
+ const actionPath = action.get(path18);
2885
2982
  if (actionPath) {
2886
2983
  actionPath.count++;
2887
2984
  return false;
2888
2985
  }
2889
2986
  let timeoutObject;
2890
2987
  const clear = () => {
2891
- const item = action.get(path12);
2988
+ const item = action.get(path18);
2892
2989
  const count = item ? item.count : 0;
2893
- action.delete(path12);
2990
+ action.delete(path18);
2894
2991
  clearTimeout(timeoutObject);
2895
2992
  if (item)
2896
2993
  clearTimeout(item.timeoutObject);
@@ -2898,7 +2995,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2898
2995
  };
2899
2996
  timeoutObject = setTimeout(clear, timeout);
2900
2997
  const thr = { timeoutObject, clear, count: 0 };
2901
- action.set(path12, thr);
2998
+ action.set(path18, thr);
2902
2999
  return thr;
2903
3000
  }
2904
3001
  _incrReadyCount() {
@@ -2912,44 +3009,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2912
3009
  * @param event
2913
3010
  * @param awfEmit Callback to be called when ready for event to be emitted.
2914
3011
  */
2915
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
3012
+ _awaitWriteFinish(path18, threshold, event, awfEmit) {
2916
3013
  const awf = this.options.awaitWriteFinish;
2917
3014
  if (typeof awf !== "object")
2918
3015
  return;
2919
3016
  const pollInterval = awf.pollInterval;
2920
3017
  let timeoutHandler;
2921
- let fullPath = path12;
2922
- if (this.options.cwd && !sp2.isAbsolute(path12)) {
2923
- fullPath = sp2.join(this.options.cwd, path12);
3018
+ let fullPath = path18;
3019
+ if (this.options.cwd && !sp2.isAbsolute(path18)) {
3020
+ fullPath = sp2.join(this.options.cwd, path18);
2924
3021
  }
2925
3022
  const now = /* @__PURE__ */ new Date();
2926
3023
  const writes = this._pendingWrites;
2927
3024
  function awaitWriteFinishFn(prevStat) {
2928
3025
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
2929
- if (err || !writes.has(path12)) {
3026
+ if (err || !writes.has(path18)) {
2930
3027
  if (err && err.code !== "ENOENT")
2931
3028
  awfEmit(err);
2932
3029
  return;
2933
3030
  }
2934
3031
  const now2 = Number(/* @__PURE__ */ new Date());
2935
3032
  if (prevStat && curStat.size !== prevStat.size) {
2936
- writes.get(path12).lastChange = now2;
3033
+ writes.get(path18).lastChange = now2;
2937
3034
  }
2938
- const pw = writes.get(path12);
3035
+ const pw = writes.get(path18);
2939
3036
  const df = now2 - pw.lastChange;
2940
3037
  if (df >= threshold) {
2941
- writes.delete(path12);
3038
+ writes.delete(path18);
2942
3039
  awfEmit(void 0, curStat);
2943
3040
  } else {
2944
3041
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2945
3042
  }
2946
3043
  });
2947
3044
  }
2948
- if (!writes.has(path12)) {
2949
- writes.set(path12, {
3045
+ if (!writes.has(path18)) {
3046
+ writes.set(path18, {
2950
3047
  lastChange: now,
2951
3048
  cancelWait: () => {
2952
- writes.delete(path12);
3049
+ writes.delete(path18);
2953
3050
  clearTimeout(timeoutHandler);
2954
3051
  return event;
2955
3052
  }
@@ -2960,8 +3057,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2960
3057
  /**
2961
3058
  * Determines whether user has asked to ignore this path.
2962
3059
  */
2963
- _isIgnored(path12, stats) {
2964
- if (this.options.atomic && DOT_RE.test(path12))
3060
+ _isIgnored(path18, stats) {
3061
+ if (this.options.atomic && DOT_RE.test(path18))
2965
3062
  return true;
2966
3063
  if (!this._userIgnored) {
2967
3064
  const { cwd } = this.options;
@@ -2971,17 +3068,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2971
3068
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2972
3069
  this._userIgnored = anymatch(list, void 0);
2973
3070
  }
2974
- return this._userIgnored(path12, stats);
3071
+ return this._userIgnored(path18, stats);
2975
3072
  }
2976
- _isntIgnored(path12, stat4) {
2977
- return !this._isIgnored(path12, stat4);
3073
+ _isntIgnored(path18, stat4) {
3074
+ return !this._isIgnored(path18, stat4);
2978
3075
  }
2979
3076
  /**
2980
3077
  * Provides a set of common helpers and properties relating to symlink handling.
2981
3078
  * @param path file or directory pattern being watched
2982
3079
  */
2983
- _getWatchHelpers(path12) {
2984
- return new WatchHelper(path12, this.options.followSymlinks, this);
3080
+ _getWatchHelpers(path18) {
3081
+ return new WatchHelper(path18, this.options.followSymlinks, this);
2985
3082
  }
2986
3083
  // Directory helpers
2987
3084
  // -----------------
@@ -3013,63 +3110,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3013
3110
  * @param item base path of item/directory
3014
3111
  */
3015
3112
  _remove(directory, item, isDirectory) {
3016
- const path12 = sp2.join(directory, item);
3017
- const fullPath = sp2.resolve(path12);
3018
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path12) || this._watched.has(fullPath);
3019
- if (!this._throttle("remove", path12, 100))
3113
+ const path18 = sp2.join(directory, item);
3114
+ const fullPath = sp2.resolve(path18);
3115
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3116
+ if (!this._throttle("remove", path18, 100))
3020
3117
  return;
3021
3118
  if (!isDirectory && this._watched.size === 1) {
3022
3119
  this.add(directory, item, true);
3023
3120
  }
3024
- const wp = this._getWatchedDir(path12);
3121
+ const wp = this._getWatchedDir(path18);
3025
3122
  const nestedDirectoryChildren = wp.getChildren();
3026
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
3123
+ nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3027
3124
  const parent = this._getWatchedDir(directory);
3028
3125
  const wasTracked = parent.has(item);
3029
3126
  parent.remove(item);
3030
3127
  if (this._symlinkPaths.has(fullPath)) {
3031
3128
  this._symlinkPaths.delete(fullPath);
3032
3129
  }
3033
- let relPath = path12;
3130
+ let relPath = path18;
3034
3131
  if (this.options.cwd)
3035
- relPath = sp2.relative(this.options.cwd, path12);
3132
+ relPath = sp2.relative(this.options.cwd, path18);
3036
3133
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3037
3134
  const event = this._pendingWrites.get(relPath).cancelWait();
3038
3135
  if (event === EVENTS.ADD)
3039
3136
  return;
3040
3137
  }
3041
- this._watched.delete(path12);
3138
+ this._watched.delete(path18);
3042
3139
  this._watched.delete(fullPath);
3043
3140
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3044
- if (wasTracked && !this._isIgnored(path12))
3045
- this._emit(eventName, path12);
3046
- this._closePath(path12);
3141
+ if (wasTracked && !this._isIgnored(path18))
3142
+ this._emit(eventName, path18);
3143
+ this._closePath(path18);
3047
3144
  }
3048
3145
  /**
3049
3146
  * Closes all watchers for a path
3050
3147
  */
3051
- _closePath(path12) {
3052
- this._closeFile(path12);
3053
- const dir = sp2.dirname(path12);
3054
- this._getWatchedDir(dir).remove(sp2.basename(path12));
3148
+ _closePath(path18) {
3149
+ this._closeFile(path18);
3150
+ const dir = sp2.dirname(path18);
3151
+ this._getWatchedDir(dir).remove(sp2.basename(path18));
3055
3152
  }
3056
3153
  /**
3057
3154
  * Closes only file-specific watchers
3058
3155
  */
3059
- _closeFile(path12) {
3060
- const closers = this._closers.get(path12);
3156
+ _closeFile(path18) {
3157
+ const closers = this._closers.get(path18);
3061
3158
  if (!closers)
3062
3159
  return;
3063
3160
  closers.forEach((closer) => closer());
3064
- this._closers.delete(path12);
3161
+ this._closers.delete(path18);
3065
3162
  }
3066
- _addPathCloser(path12, closer) {
3163
+ _addPathCloser(path18, closer) {
3067
3164
  if (!closer)
3068
3165
  return;
3069
- let list = this._closers.get(path12);
3166
+ let list = this._closers.get(path18);
3070
3167
  if (!list) {
3071
3168
  list = [];
3072
- this._closers.set(path12, list);
3169
+ this._closers.set(path18, list);
3073
3170
  }
3074
3171
  list.push(closer);
3075
3172
  }
@@ -3098,13 +3195,13 @@ function watch(paths, options = {}) {
3098
3195
  }
3099
3196
  var chokidar_default = { watch, FSWatcher };
3100
3197
 
3101
- // src/watcher/index.ts
3102
- var path5 = __toESM(require("path"), 1);
3198
+ // src/watcher/file-watcher.ts
3199
+ var path8 = __toESM(require("path"), 1);
3103
3200
 
3104
3201
  // src/utils/files.ts
3105
3202
  var import_ignore = __toESM(require_ignore(), 1);
3106
- var import_fs4 = require("fs");
3107
- var path4 = __toESM(require("path"), 1);
3203
+ var import_fs5 = require("fs");
3204
+ var path7 = __toESM(require("path"), 1);
3108
3205
  var PROJECT_MARKERS = [
3109
3206
  ".git",
3110
3207
  "package.json",
@@ -3123,7 +3220,7 @@ var PROJECT_MARKERS = [
3123
3220
  ];
3124
3221
  function hasProjectMarker(projectRoot) {
3125
3222
  for (const marker of PROJECT_MARKERS) {
3126
- if ((0, import_fs4.existsSync)(path4.join(projectRoot, marker))) {
3223
+ if ((0, import_fs5.existsSync)(path7.join(projectRoot, marker))) {
3127
3224
  return true;
3128
3225
  }
3129
3226
  }
@@ -3149,23 +3246,17 @@ function createIgnoreFilter(projectRoot) {
3149
3246
  "**/*build*/**"
3150
3247
  ];
3151
3248
  ig.add(defaultIgnores);
3152
- const gitignorePath = path4.join(projectRoot, ".gitignore");
3153
- if ((0, import_fs4.existsSync)(gitignorePath)) {
3154
- const gitignoreContent = (0, import_fs4.readFileSync)(gitignorePath, "utf-8");
3249
+ const gitignorePath = path7.join(projectRoot, ".gitignore");
3250
+ if ((0, import_fs5.existsSync)(gitignorePath)) {
3251
+ const gitignoreContent = (0, import_fs5.readFileSync)(gitignorePath, "utf-8");
3155
3252
  ig.add(gitignoreContent);
3156
3253
  }
3157
3254
  return ig;
3158
3255
  }
3159
3256
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3160
- const relativePath = path4.relative(projectRoot, filePath);
3161
- const pathParts = relativePath.split(path4.sep);
3162
- for (const part of pathParts) {
3163
- if (part.startsWith(".") && part !== "." && part !== "..") {
3164
- return false;
3165
- }
3166
- if (part.toLowerCase().includes("build")) {
3167
- return false;
3168
- }
3257
+ const relativePath = path7.relative(projectRoot, filePath);
3258
+ if (hasFilteredPathSegment(relativePath, path7.sep)) {
3259
+ return false;
3169
3260
  }
3170
3261
  if (ignoreFilter.ignores(relativePath)) {
3171
3262
  return false;
@@ -3198,19 +3289,19 @@ function matchGlob(filePath, pattern) {
3198
3289
  return regex.test(filePath);
3199
3290
  }
3200
3291
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3201
- const entries = await import_fs4.promises.readdir(dir, { withFileTypes: true });
3292
+ const entries = await import_fs5.promises.readdir(dir, { withFileTypes: true });
3202
3293
  const filesInDir = [];
3203
3294
  const subdirs = [];
3204
3295
  for (const entry of entries) {
3205
- const fullPath = path4.join(dir, entry.name);
3206
- const relativePath = path4.relative(projectRoot, fullPath);
3207
- if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
3296
+ const fullPath = path7.join(dir, entry.name);
3297
+ const relativePath = path7.relative(projectRoot, fullPath);
3298
+ if (isHiddenPathSegment(entry.name)) {
3208
3299
  if (entry.isDirectory()) {
3209
3300
  skipped.push({ path: relativePath, reason: "excluded" });
3210
3301
  }
3211
3302
  continue;
3212
3303
  }
3213
- if (entry.isDirectory() && entry.name.toLowerCase().includes("build")) {
3304
+ if (entry.isDirectory() && isBuildPathSegment(entry.name)) {
3214
3305
  skipped.push({ path: relativePath, reason: "excluded" });
3215
3306
  continue;
3216
3307
  }
@@ -3223,7 +3314,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3223
3314
  if (entry.isDirectory()) {
3224
3315
  subdirs.push({ fullPath, relativePath });
3225
3316
  } else if (entry.isFile()) {
3226
- const stat4 = await import_fs4.promises.stat(fullPath);
3317
+ const stat4 = await import_fs5.promises.stat(fullPath);
3227
3318
  if (stat4.size > maxFileSize) {
3228
3319
  skipped.push({ path: relativePath, reason: "too_large" });
3229
3320
  continue;
@@ -3252,7 +3343,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3252
3343
  yield f;
3253
3344
  }
3254
3345
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3255
- skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3346
+ skipped.push({ path: path7.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3256
3347
  }
3257
3348
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3258
3349
  if (canRecurse) {
@@ -3292,14 +3383,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3292
3383
  if (additionalRoots && additionalRoots.length > 0) {
3293
3384
  const normalizedRoots = /* @__PURE__ */ new Set();
3294
3385
  for (const kbRoot of additionalRoots) {
3295
- const resolved = path4.normalize(
3296
- path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3386
+ const resolved = path7.normalize(
3387
+ path7.isAbsolute(kbRoot) ? kbRoot : path7.resolve(projectRoot, kbRoot)
3297
3388
  );
3298
3389
  normalizedRoots.add(resolved);
3299
3390
  }
3300
3391
  for (const resolvedKbRoot of normalizedRoots) {
3301
3392
  try {
3302
- const stat4 = await import_fs4.promises.stat(resolvedKbRoot);
3393
+ const stat4 = await import_fs5.promises.stat(resolvedKbRoot);
3303
3394
  if (!stat4.isDirectory()) {
3304
3395
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3305
3396
  continue;
@@ -3326,7 +3417,7 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3326
3417
  return { files, skipped };
3327
3418
  }
3328
3419
 
3329
- // src/watcher/index.ts
3420
+ // src/watcher/file-watcher.ts
3330
3421
  var FileWatcher = class {
3331
3422
  watcher = null;
3332
3423
  projectRoot;
@@ -3347,16 +3438,13 @@ var FileWatcher = class {
3347
3438
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3348
3439
  this.watcher = chokidar_default.watch(this.projectRoot, {
3349
3440
  ignored: (filePath) => {
3350
- const relativePath = path5.relative(this.projectRoot, filePath);
3441
+ const relativePath = path8.relative(this.projectRoot, filePath);
3351
3442
  if (!relativePath) return false;
3352
- const pathParts = relativePath.split(path5.sep);
3353
- for (const part of pathParts) {
3354
- if (part.startsWith(".") && part !== "." && part !== "..") {
3355
- return true;
3356
- }
3357
- if (part.toLowerCase().includes("build")) {
3358
- return true;
3359
- }
3443
+ if (hasFilteredPathSegment(relativePath, path8.sep)) {
3444
+ return true;
3445
+ }
3446
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3447
+ return true;
3360
3448
  }
3361
3449
  if (ignoreFilter.ignores(relativePath)) {
3362
3450
  return true;
@@ -3370,6 +3458,13 @@ var FileWatcher = class {
3370
3458
  pollInterval: 100
3371
3459
  }
3372
3460
  });
3461
+ this.watcher.on("error", (error) => {
3462
+ const err = error instanceof Error ? error : null;
3463
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3464
+ return;
3465
+ }
3466
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3467
+ });
3373
3468
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3374
3469
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3375
3470
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3401,7 +3496,7 @@ var FileWatcher = class {
3401
3496
  return;
3402
3497
  }
3403
3498
  const changes = Array.from(this.pendingChanges.entries()).map(
3404
- ([path12, type]) => ({ path: path12, type })
3499
+ ([path18, type]) => ({ path: path18, type })
3405
3500
  );
3406
3501
  this.pendingChanges.clear();
3407
3502
  try {
@@ -3426,6 +3521,9 @@ var FileWatcher = class {
3426
3521
  return this.watcher !== null;
3427
3522
  }
3428
3523
  };
3524
+
3525
+ // src/watcher/git-head-watcher.ts
3526
+ var path9 = __toESM(require("path"), 1);
3429
3527
  var GitHeadWatcher = class {
3430
3528
  watcher = null;
3431
3529
  projectRoot;
@@ -3447,7 +3545,7 @@ var GitHeadWatcher = class {
3447
3545
  this.onBranchChange = handler;
3448
3546
  this.currentBranch = getCurrentBranch(this.projectRoot);
3449
3547
  const headPath = getHeadPath(this.projectRoot);
3450
- const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3548
+ const refsPath = path9.join(this.projectRoot, ".git", "refs", "heads");
3451
3549
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3452
3550
  persistent: true,
3453
3551
  ignoreInitial: true,
@@ -3499,7 +3597,9 @@ var GitHeadWatcher = class {
3499
3597
  return this.watcher !== null;
3500
3598
  }
3501
3599
  };
3502
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3600
+
3601
+ // src/watcher/index.ts
3602
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3503
3603
  const fileWatcher = new FileWatcher(projectRoot, config);
3504
3604
  fileWatcher.start(async (changes) => {
3505
3605
  const hasAddOrChange = changes.some(
@@ -3507,7 +3607,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3507
3607
  );
3508
3608
  const hasDelete = changes.some((c) => c.type === "unlink");
3509
3609
  if (hasAddOrChange || hasDelete) {
3510
- await getIndexer2().index();
3610
+ await getIndexer().index();
3511
3611
  }
3512
3612
  });
3513
3613
  let gitWatcher = null;
@@ -3515,7 +3615,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3515
3615
  gitWatcher = new GitHeadWatcher(projectRoot);
3516
3616
  gitWatcher.start(async (oldBranch, newBranch) => {
3517
3617
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3518
- await getIndexer2().index();
3618
+ await getIndexer().index();
3519
3619
  });
3520
3620
  }
3521
3621
  return {
@@ -3532,8 +3632,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3532
3632
  var import_plugin = require("@opencode-ai/plugin");
3533
3633
 
3534
3634
  // src/indexer/index.ts
3535
- var import_fs6 = require("fs");
3536
- var path8 = __toESM(require("path"), 1);
3635
+ var import_fs7 = require("fs");
3636
+ var path12 = __toESM(require("path"), 1);
3537
3637
  var import_perf_hooks = require("perf_hooks");
3538
3638
 
3539
3639
  // node_modules/eventemitter3/index.mjs
@@ -3558,7 +3658,7 @@ function pTimeout(promise, options) {
3558
3658
  } = options;
3559
3659
  let timer;
3560
3660
  let abortHandler;
3561
- const wrappedPromise = new Promise((resolve9, reject) => {
3661
+ const wrappedPromise = new Promise((resolve12, reject) => {
3562
3662
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3563
3663
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3564
3664
  }
@@ -3572,7 +3672,7 @@ function pTimeout(promise, options) {
3572
3672
  };
3573
3673
  signal.addEventListener("abort", abortHandler, { once: true });
3574
3674
  }
3575
- promise.then(resolve9, reject);
3675
+ promise.then(resolve12, reject);
3576
3676
  if (milliseconds === Number.POSITIVE_INFINITY) {
3577
3677
  return;
3578
3678
  }
@@ -3580,7 +3680,7 @@ function pTimeout(promise, options) {
3580
3680
  timer = customTimers.setTimeout.call(void 0, () => {
3581
3681
  if (fallback) {
3582
3682
  try {
3583
- resolve9(fallback());
3683
+ resolve12(fallback());
3584
3684
  } catch (error) {
3585
3685
  reject(error);
3586
3686
  }
@@ -3590,7 +3690,7 @@ function pTimeout(promise, options) {
3590
3690
  promise.cancel();
3591
3691
  }
3592
3692
  if (message === false) {
3593
- resolve9();
3693
+ resolve12();
3594
3694
  } else if (message instanceof Error) {
3595
3695
  reject(message);
3596
3696
  } else {
@@ -3992,7 +4092,7 @@ var PQueue = class extends import_index.default {
3992
4092
  // Assign unique ID if not provided
3993
4093
  id: options.id ?? (this.#idAssigner++).toString()
3994
4094
  };
3995
- return new Promise((resolve9, reject) => {
4095
+ return new Promise((resolve12, reject) => {
3996
4096
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3997
4097
  let cleanupQueueAbortHandler = () => void 0;
3998
4098
  const run = async () => {
@@ -4032,7 +4132,7 @@ var PQueue = class extends import_index.default {
4032
4132
  })]);
4033
4133
  }
4034
4134
  const result = await operation;
4035
- resolve9(result);
4135
+ resolve12(result);
4036
4136
  this.emit("completed", result);
4037
4137
  } catch (error) {
4038
4138
  reject(error);
@@ -4220,13 +4320,13 @@ var PQueue = class extends import_index.default {
4220
4320
  });
4221
4321
  }
4222
4322
  async #onEvent(event, filter) {
4223
- return new Promise((resolve9) => {
4323
+ return new Promise((resolve12) => {
4224
4324
  const listener = () => {
4225
4325
  if (filter && !filter()) {
4226
4326
  return;
4227
4327
  }
4228
4328
  this.off(event, listener);
4229
- resolve9();
4329
+ resolve12();
4230
4330
  };
4231
4331
  this.on(event, listener);
4232
4332
  });
@@ -4512,7 +4612,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4512
4612
  const finalDelay = Math.min(delayTime, remainingTime);
4513
4613
  options.signal?.throwIfAborted();
4514
4614
  if (finalDelay > 0) {
4515
- await new Promise((resolve9, reject) => {
4615
+ await new Promise((resolve12, reject) => {
4516
4616
  const onAbort = () => {
4517
4617
  clearTimeout(timeoutToken);
4518
4618
  options.signal?.removeEventListener("abort", onAbort);
@@ -4520,7 +4620,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4520
4620
  };
4521
4621
  const timeoutToken = setTimeout(() => {
4522
4622
  options.signal?.removeEventListener("abort", onAbort);
4523
- resolve9();
4623
+ resolve12();
4524
4624
  }, finalDelay);
4525
4625
  if (options.unref) {
4526
4626
  timeoutToken.unref?.();
@@ -4581,17 +4681,17 @@ async function pRetry(input, options = {}) {
4581
4681
  }
4582
4682
 
4583
4683
  // src/embeddings/detector.ts
4584
- var import_fs5 = require("fs");
4585
- var path6 = __toESM(require("path"), 1);
4684
+ var import_fs6 = require("fs");
4685
+ var path10 = __toESM(require("path"), 1);
4586
4686
  var os3 = __toESM(require("os"), 1);
4587
4687
  function getOpenCodeAuthPath() {
4588
- return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4688
+ return path10.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4589
4689
  }
4590
4690
  function loadOpenCodeAuth() {
4591
4691
  const authPath = getOpenCodeAuthPath();
4592
4692
  try {
4593
- if ((0, import_fs5.existsSync)(authPath)) {
4594
- return JSON.parse((0, import_fs5.readFileSync)(authPath, "utf-8"));
4693
+ if ((0, import_fs6.existsSync)(authPath)) {
4694
+ return JSON.parse((0, import_fs6.readFileSync)(authPath, "utf-8"));
4595
4695
  }
4596
4696
  } catch {
4597
4697
  }
@@ -4658,7 +4758,8 @@ function getGitHubCopilotCredentials() {
4658
4758
  if (!copilotAuth || copilotAuth.type !== "oauth") {
4659
4759
  return null;
4660
4760
  }
4661
- const baseUrl = copilotAuth.enterpriseUrl ? `https://copilot-api.${copilotAuth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4761
+ const auth = copilotAuth;
4762
+ const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4662
4763
  return {
4663
4764
  provider: "github-copilot",
4664
4765
  baseUrl,
@@ -4754,42 +4855,12 @@ function createCustomProviderInfo(config) {
4754
4855
  };
4755
4856
  }
4756
4857
 
4757
- // src/embeddings/provider.ts
4758
- var CustomProviderNonRetryableError = class extends Error {
4759
- constructor(message) {
4760
- super(message);
4761
- this.name = "CustomProviderNonRetryableError";
4762
- }
4763
- };
4764
- function createEmbeddingProvider(configuredProviderInfo) {
4765
- switch (configuredProviderInfo.provider) {
4766
- case "github-copilot":
4767
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4768
- case "openai":
4769
- return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4770
- case "google":
4771
- return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4772
- case "ollama":
4773
- return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4774
- case "custom":
4775
- return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4776
- default: {
4777
- const _exhaustive = configuredProviderInfo;
4778
- throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
4779
- }
4780
- }
4781
- }
4782
- var GitHubCopilotEmbeddingProvider = class {
4858
+ // src/embeddings/provider-types.ts
4859
+ var BaseEmbeddingProvider = class {
4783
4860
  constructor(credentials, modelInfo) {
4784
4861
  this.credentials = credentials;
4785
4862
  this.modelInfo = modelInfo;
4786
4863
  }
4787
- getToken() {
4788
- if (!this.credentials.refreshToken) {
4789
- throw new Error("No OAuth token available for GitHub");
4790
- }
4791
- return this.credentials.refreshToken;
4792
- }
4793
4864
  async embedQuery(query) {
4794
4865
  const result = await this.embedBatch([query]);
4795
4866
  return {
@@ -4804,69 +4875,204 @@ var GitHubCopilotEmbeddingProvider = class {
4804
4875
  tokensUsed: result.totalTokensUsed
4805
4876
  };
4806
4877
  }
4807
- async embedBatch(texts) {
4808
- const token = this.getToken();
4809
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4810
- method: "POST",
4811
- headers: {
4812
- Authorization: `Bearer ${token}`,
4813
- "Content-Type": "application/json",
4814
- Accept: "application/vnd.github+json",
4815
- "X-GitHub-Api-Version": "2022-11-28"
4816
- },
4817
- body: JSON.stringify({
4818
- model: `openai/${this.modelInfo.model}`,
4819
- input: texts
4820
- })
4821
- });
4822
- if (!response.ok) {
4823
- const error = await response.text();
4824
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4825
- }
4826
- const data = await response.json();
4827
- return {
4828
- embeddings: data.data.map((d) => d.embedding),
4829
- totalTokensUsed: data.usage.total_tokens
4830
- };
4831
- }
4832
4878
  getModelInfo() {
4833
4879
  return this.modelInfo;
4834
4880
  }
4835
4881
  };
4836
- var OpenAIEmbeddingProvider = class {
4882
+ var CustomProviderNonRetryableError = class extends Error {
4883
+ constructor(message) {
4884
+ super(message);
4885
+ this.name = "CustomProviderNonRetryableError";
4886
+ }
4887
+ };
4888
+
4889
+ // src/utils/url-validation.ts
4890
+ var BLOCKED_METADATA_IPS = [
4891
+ /^169\.254\.169\.254$/,
4892
+ // AWS/Azure/GCP metadata
4893
+ /^169\.254\.170\.2$/,
4894
+ // AWS ECS task metadata
4895
+ /^fd00:ec2::254$/
4896
+ // AWS IMDSv2 IPv6
4897
+ ];
4898
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
4899
+ "metadata.google.internal",
4900
+ "metadata.google",
4901
+ "metadata.goog",
4902
+ "kubernetes.default.svc"
4903
+ ]);
4904
+ function validateExternalUrl(urlString) {
4905
+ let parsed;
4906
+ try {
4907
+ parsed = new URL(urlString);
4908
+ } catch {
4909
+ return { valid: false, reason: `Invalid URL: ${sanitizeUrlForError(urlString)}` };
4910
+ }
4911
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4912
+ return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
4913
+ }
4914
+ const hostname = parsed.hostname.toLowerCase();
4915
+ if (BLOCKED_HOSTNAMES.has(hostname)) {
4916
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
4917
+ }
4918
+ for (const pattern of BLOCKED_METADATA_IPS) {
4919
+ if (pattern.test(hostname)) {
4920
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
4921
+ }
4922
+ }
4923
+ if (/^169\.254\./.test(hostname)) {
4924
+ return { valid: false, reason: `Blocked: link-local address (${hostname})` };
4925
+ }
4926
+ return { valid: true };
4927
+ }
4928
+ function sanitizeUrlForError(url) {
4929
+ try {
4930
+ const parsed = new URL(url);
4931
+ parsed.username = "";
4932
+ parsed.password = "";
4933
+ return parsed.toString();
4934
+ } catch {
4935
+ const maxLen = 80;
4936
+ if (url.length > maxLen) {
4937
+ return url.slice(0, maxLen) + "...";
4938
+ }
4939
+ return url;
4940
+ }
4941
+ }
4942
+
4943
+ // src/embeddings/providers/custom.ts
4944
+ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
4837
4945
  constructor(credentials, modelInfo) {
4838
- this.credentials = credentials;
4839
- this.modelInfo = modelInfo;
4946
+ super(credentials, modelInfo);
4840
4947
  }
4841
- async embedQuery(query) {
4842
- const result = await this.embedBatch([query]);
4843
- return {
4844
- embedding: result.embeddings[0],
4845
- tokensUsed: result.totalTokensUsed
4948
+ splitIntoRequestBatches(texts) {
4949
+ const maxBatchSize = this.modelInfo.maxBatchSize;
4950
+ if (!maxBatchSize || texts.length <= maxBatchSize) {
4951
+ return [texts];
4952
+ }
4953
+ const batches = [];
4954
+ for (let i = 0; i < texts.length; i += maxBatchSize) {
4955
+ batches.push(texts.slice(i, i + maxBatchSize));
4956
+ }
4957
+ return batches;
4958
+ }
4959
+ async embedRequest(texts) {
4960
+ if (texts.length === 0) {
4961
+ return {
4962
+ embeddings: [],
4963
+ totalTokensUsed: 0
4964
+ };
4965
+ }
4966
+ const headers = {
4967
+ "Content-Type": "application/json"
4846
4968
  };
4969
+ if (this.credentials.apiKey) {
4970
+ headers.Authorization = `Bearer ${this.credentials.apiKey}`;
4971
+ }
4972
+ const baseUrl = this.credentials.baseUrl ?? "";
4973
+ const fullUrl = `${baseUrl}/embeddings`;
4974
+ const urlCheck = validateExternalUrl(fullUrl);
4975
+ if (!urlCheck.valid) {
4976
+ throw new CustomProviderNonRetryableError(
4977
+ `Custom embedding provider URL blocked (SSRF protection): ${urlCheck.reason}`
4978
+ );
4979
+ }
4980
+ const timeoutMs = this.modelInfo.timeoutMs;
4981
+ const controller = new AbortController();
4982
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
4983
+ let response;
4984
+ try {
4985
+ response = await fetch(fullUrl, {
4986
+ method: "POST",
4987
+ headers,
4988
+ body: JSON.stringify({
4989
+ model: this.modelInfo.model,
4990
+ input: texts
4991
+ }),
4992
+ signal: controller.signal
4993
+ });
4994
+ } catch (error) {
4995
+ if (error instanceof Error && error.name === "AbortError") {
4996
+ throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${sanitizeUrlForError(fullUrl)}`);
4997
+ }
4998
+ throw error;
4999
+ } finally {
5000
+ clearTimeout(timeout);
5001
+ }
5002
+ if (!response.ok) {
5003
+ const errorText = (await response.text()).slice(0, 500);
5004
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
5005
+ throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
5006
+ }
5007
+ throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
5008
+ }
5009
+ const data = await response.json();
5010
+ if (data.data && Array.isArray(data.data)) {
5011
+ if (data.data.length > 0) {
5012
+ const actualDims = data.data[0].embedding.length;
5013
+ if (actualDims !== this.modelInfo.dimensions) {
5014
+ throw new Error(
5015
+ `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.`
5016
+ );
5017
+ }
5018
+ }
5019
+ if (data.data.length !== texts.length) {
5020
+ throw new Error(
5021
+ `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
5022
+ );
5023
+ }
5024
+ return {
5025
+ embeddings: data.data.map((d) => d.embedding),
5026
+ totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
5027
+ };
5028
+ }
5029
+ throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
4847
5030
  }
4848
- async embedDocument(document) {
4849
- const result = await this.embedBatch([document]);
5031
+ async embedBatch(texts) {
5032
+ const requestBatches = this.splitIntoRequestBatches(texts);
5033
+ const embeddings = [];
5034
+ let totalTokensUsed = 0;
5035
+ for (const batch of requestBatches) {
5036
+ const result = await this.embedRequest(batch);
5037
+ embeddings.push(...result.embeddings);
5038
+ totalTokensUsed += result.totalTokensUsed;
5039
+ }
4850
5040
  return {
4851
- embedding: result.embeddings[0],
4852
- tokensUsed: result.totalTokensUsed
5041
+ embeddings,
5042
+ totalTokensUsed
4853
5043
  };
4854
5044
  }
5045
+ };
5046
+
5047
+ // src/embeddings/providers/github-copilot.ts
5048
+ var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
5049
+ constructor(credentials, modelInfo) {
5050
+ super(credentials, modelInfo);
5051
+ }
5052
+ getToken() {
5053
+ if (!this.credentials.refreshToken) {
5054
+ throw new Error("No OAuth token available for GitHub");
5055
+ }
5056
+ return this.credentials.refreshToken;
5057
+ }
4855
5058
  async embedBatch(texts) {
4856
- const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5059
+ const token = this.getToken();
5060
+ const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4857
5061
  method: "POST",
4858
5062
  headers: {
4859
- Authorization: `Bearer ${this.credentials.apiKey}`,
4860
- "Content-Type": "application/json"
5063
+ Authorization: `Bearer ${token}`,
5064
+ "Content-Type": "application/json",
5065
+ Accept: "application/vnd.github+json",
5066
+ "X-GitHub-Api-Version": "2022-11-28"
4861
5067
  },
4862
5068
  body: JSON.stringify({
4863
- model: this.modelInfo.model,
5069
+ model: `openai/${this.modelInfo.model}`,
4864
5070
  input: texts
4865
5071
  })
4866
5072
  });
4867
5073
  if (!response.ok) {
4868
- const error = await response.text();
4869
- throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5074
+ const error = (await response.text()).slice(0, 500);
5075
+ throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4870
5076
  }
4871
5077
  const data = await response.json();
4872
5078
  return {
@@ -4874,16 +5080,14 @@ var OpenAIEmbeddingProvider = class {
4874
5080
  totalTokensUsed: data.usage.total_tokens
4875
5081
  };
4876
5082
  }
4877
- getModelInfo() {
4878
- return this.modelInfo;
4879
- }
4880
5083
  };
4881
- var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
5084
+
5085
+ // src/embeddings/providers/google.ts
5086
+ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
5087
+ static BATCH_SIZE = 20;
4882
5088
  constructor(credentials, modelInfo) {
4883
- this.credentials = credentials;
4884
- this.modelInfo = modelInfo;
5089
+ super(credentials, modelInfo);
4885
5090
  }
4886
- static BATCH_SIZE = 20;
4887
5091
  async embedQuery(query) {
4888
5092
  const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
4889
5093
  const result = await this.embedWithTaskType([query], taskType);
@@ -4904,12 +5108,6 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4904
5108
  const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
4905
5109
  return this.embedWithTaskType(texts, taskType);
4906
5110
  }
4907
- /**
4908
- * Embeds texts using the Google embedContent API.
4909
- * Sends multiple texts as parts in batched requests (up to BATCH_SIZE per call).
4910
- * When taskType is provided (gemini-embedding-001), includes it in the request
4911
- * for task-specific embedding optimization.
4912
- */
4913
5111
  async embedWithTaskType(texts, taskType) {
4914
5112
  const batches = [];
4915
5113
  for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
@@ -4926,17 +5124,18 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4926
5124
  outputDimensionality: this.modelInfo.dimensions
4927
5125
  }));
4928
5126
  const response = await fetch(
4929
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents?key=${this.credentials.apiKey}`,
5127
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents`,
4930
5128
  {
4931
5129
  method: "POST",
4932
5130
  headers: {
4933
- "Content-Type": "application/json"
5131
+ "Content-Type": "application/json",
5132
+ ...this.credentials.apiKey && { "x-goog-api-key": this.credentials.apiKey }
4934
5133
  },
4935
5134
  body: JSON.stringify({ requests })
4936
5135
  }
4937
5136
  );
4938
5137
  if (!response.ok) {
4939
- const error = await response.text();
5138
+ const error = (await response.text()).slice(0, 500);
4940
5139
  throw new Error(`Google embedding API error: ${response.status} - ${error}`);
4941
5140
  }
4942
5141
  const data = await response.json();
@@ -4951,29 +5150,13 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4951
5150
  totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
4952
5151
  };
4953
5152
  }
4954
- getModelInfo() {
4955
- return this.modelInfo;
4956
- }
4957
5153
  };
4958
- var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4959
- constructor(credentials, modelInfo) {
4960
- this.credentials = credentials;
4961
- this.modelInfo = modelInfo;
4962
- }
5154
+
5155
+ // src/embeddings/providers/ollama.ts
5156
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
4963
5157
  static MIN_TRUNCATION_CHARS = 512;
4964
- async embedQuery(query) {
4965
- const result = await this.embedBatch([query]);
4966
- return {
4967
- embedding: result.embeddings[0],
4968
- tokensUsed: result.totalTokensUsed
4969
- };
4970
- }
4971
- async embedDocument(document) {
4972
- const result = await this.embedBatch([document]);
4973
- return {
4974
- embedding: result.embeddings[0],
4975
- tokensUsed: result.totalTokensUsed
4976
- };
5158
+ constructor(credentials, modelInfo) {
5159
+ super(credentials, modelInfo);
4977
5160
  }
4978
5161
  estimateTokens(text) {
4979
5162
  return Math.ceil(text.length / 4);
@@ -5042,161 +5225,92 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
5042
5225
  lastError = retryError;
5043
5226
  }
5044
5227
  }
5045
- throw lastError;
5046
- }
5047
- }
5048
- async embedSingle(text) {
5049
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5050
- method: "POST",
5051
- headers: {
5052
- "Content-Type": "application/json"
5053
- },
5054
- body: JSON.stringify({
5055
- model: this.modelInfo.model,
5056
- prompt: text,
5057
- truncate: false
5058
- })
5059
- });
5060
- if (!response.ok) {
5061
- const error = await response.text();
5062
- throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5063
- }
5064
- const data = await response.json();
5065
- return {
5066
- embedding: data.embedding,
5067
- tokensUsed: this.estimateTokens(text)
5068
- };
5069
- }
5070
- async embedBatch(texts) {
5071
- const results = [];
5072
- for (const text of texts) {
5073
- results.push(await this.embedSingleWithFallback(text));
5074
- }
5075
- return {
5076
- embeddings: results.map((r) => r.embedding),
5077
- totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5078
- };
5079
- }
5080
- getModelInfo() {
5081
- return this.modelInfo;
5082
- }
5083
- };
5084
- var CustomEmbeddingProvider = class {
5085
- constructor(credentials, modelInfo) {
5086
- this.credentials = credentials;
5087
- this.modelInfo = modelInfo;
5088
- }
5089
- splitIntoRequestBatches(texts) {
5090
- const maxBatchSize = this.modelInfo.maxBatchSize;
5091
- if (!maxBatchSize || texts.length <= maxBatchSize) {
5092
- return [texts];
5093
- }
5094
- const batches = [];
5095
- for (let i = 0; i < texts.length; i += maxBatchSize) {
5096
- batches.push(texts.slice(i, i + maxBatchSize));
5097
- }
5098
- return batches;
5099
- }
5100
- async embedRequest(texts) {
5101
- if (texts.length === 0) {
5102
- return {
5103
- embeddings: [],
5104
- totalTokensUsed: 0
5105
- };
5106
- }
5107
- const headers = {
5108
- "Content-Type": "application/json"
5109
- };
5110
- if (this.credentials.apiKey) {
5111
- headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
5112
- }
5113
- const baseUrl = this.credentials.baseUrl ?? "";
5114
- const timeoutMs = this.modelInfo.timeoutMs;
5115
- const controller = new AbortController();
5116
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
5117
- let response;
5118
- try {
5119
- response = await fetch(`${baseUrl}/embeddings`, {
5120
- method: "POST",
5121
- headers,
5122
- body: JSON.stringify({
5123
- model: this.modelInfo.model,
5124
- input: texts
5125
- }),
5126
- signal: controller.signal
5127
- });
5128
- } catch (error) {
5129
- if (error instanceof Error && error.name === "AbortError") {
5130
- throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
5131
- }
5132
- throw error;
5133
- } finally {
5134
- clearTimeout(timeout);
5135
- }
5136
- if (!response.ok) {
5137
- const errorText = await response.text();
5138
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
5139
- throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
5140
- }
5141
- throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
5142
- }
5143
- const data = await response.json();
5144
- if (data.data && Array.isArray(data.data)) {
5145
- if (data.data.length > 0) {
5146
- const actualDims = data.data[0].embedding.length;
5147
- if (actualDims !== this.modelInfo.dimensions) {
5148
- throw new Error(
5149
- `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.`
5150
- );
5151
- }
5152
- }
5153
- if (data.data.length !== texts.length) {
5154
- throw new Error(
5155
- `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
5156
- );
5157
- }
5158
- return {
5159
- embeddings: data.data.map((d) => d.embedding),
5160
- // Rough estimate: ~4 chars per token. Used as fallback when the server
5161
- // doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
5162
- totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
5163
- };
5228
+ throw lastError;
5164
5229
  }
5165
- throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
5166
5230
  }
5167
- async embedQuery(query) {
5168
- const result = await this.embedBatch([query]);
5231
+ async embedSingle(text) {
5232
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5233
+ method: "POST",
5234
+ headers: {
5235
+ "Content-Type": "application/json"
5236
+ },
5237
+ body: JSON.stringify({
5238
+ model: this.modelInfo.model,
5239
+ prompt: text,
5240
+ truncate: false
5241
+ })
5242
+ });
5243
+ if (!response.ok) {
5244
+ const error = (await response.text()).slice(0, 500);
5245
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5246
+ }
5247
+ const data = await response.json();
5169
5248
  return {
5170
- embedding: result.embeddings[0],
5171
- tokensUsed: result.totalTokensUsed
5249
+ embedding: data.embedding,
5250
+ tokensUsed: this.estimateTokens(text)
5172
5251
  };
5173
5252
  }
5174
- async embedDocument(document) {
5175
- const result = await this.embedBatch([document]);
5253
+ async embedBatch(texts) {
5254
+ const results = [];
5255
+ for (const text of texts) {
5256
+ results.push(await this.embedSingleWithFallback(text));
5257
+ }
5176
5258
  return {
5177
- embedding: result.embeddings[0],
5178
- tokensUsed: result.totalTokensUsed
5259
+ embeddings: results.map((r) => r.embedding),
5260
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5179
5261
  };
5180
5262
  }
5263
+ };
5264
+
5265
+ // src/embeddings/providers/openai.ts
5266
+ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
5267
+ constructor(credentials, modelInfo) {
5268
+ super(credentials, modelInfo);
5269
+ }
5181
5270
  async embedBatch(texts) {
5182
- const requestBatches = this.splitIntoRequestBatches(texts);
5183
- const embeddings = [];
5184
- let totalTokensUsed = 0;
5185
- for (const batch of requestBatches) {
5186
- const result = await this.embedRequest(batch);
5187
- embeddings.push(...result.embeddings);
5188
- totalTokensUsed += result.totalTokensUsed;
5271
+ const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5272
+ method: "POST",
5273
+ headers: {
5274
+ Authorization: `Bearer ${this.credentials.apiKey}`,
5275
+ "Content-Type": "application/json"
5276
+ },
5277
+ body: JSON.stringify({
5278
+ model: this.modelInfo.model,
5279
+ input: texts
5280
+ })
5281
+ });
5282
+ if (!response.ok) {
5283
+ const error = (await response.text()).slice(0, 500);
5284
+ throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5189
5285
  }
5286
+ const data = await response.json();
5190
5287
  return {
5191
- embeddings,
5192
- totalTokensUsed
5288
+ embeddings: data.data.map((d) => d.embedding),
5289
+ totalTokensUsed: data.usage.total_tokens
5193
5290
  };
5194
5291
  }
5195
- getModelInfo() {
5196
- return this.modelInfo;
5197
- }
5198
5292
  };
5199
5293
 
5294
+ // src/embeddings/provider.ts
5295
+ function createEmbeddingProvider(configuredProviderInfo) {
5296
+ switch (configuredProviderInfo.provider) {
5297
+ case "github-copilot":
5298
+ return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5299
+ case "openai":
5300
+ return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5301
+ case "google":
5302
+ return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5303
+ case "ollama":
5304
+ return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5305
+ case "custom":
5306
+ return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5307
+ default: {
5308
+ const _exhaustive = configuredProviderInfo;
5309
+ throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
5310
+ }
5311
+ }
5312
+ }
5313
+
5200
5314
  // src/rerank/index.ts
5201
5315
  function createReranker(config) {
5202
5316
  if (!config.enabled) {
@@ -5232,7 +5346,10 @@ var SiliconFlowReranker = class {
5232
5346
  if (this.config.apiKey) {
5233
5347
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
5234
5348
  }
5235
- const baseUrl = this.config.baseUrl ?? "https://api.siliconflow.cn/v1";
5349
+ const baseUrl = this.config.baseUrl;
5350
+ if (!baseUrl) {
5351
+ throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
5352
+ }
5236
5353
  const timeoutMs = this.config.timeoutMs ?? 3e4;
5237
5354
  const controller = new AbortController();
5238
5355
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -5306,20 +5423,21 @@ function createCostEstimate(files, provider) {
5306
5423
  }
5307
5424
  function formatCostEstimate(estimate) {
5308
5425
  const sizeFormatted = formatBytes(estimate.totalSizeBytes);
5426
+ const filesFormatted = `${estimate.filesCount.toLocaleString()} files`;
5309
5427
  const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
5310
5428
  return `
5311
5429
  \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
5312
5430
  \u2502 \u{1F4CA} Indexing Estimate \u2502
5313
5431
  \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
5314
5432
  \u2502 \u2502
5315
- \u2502 Files to index: ${padRight(estimate.filesCount.toLocaleString() + " files", 40)}\u2502
5316
- \u2502 Total size: ${padRight(sizeFormatted, 40)}\u2502
5317
- \u2502 Estimated chunks: ${padRight("~" + estimate.estimatedChunks.toLocaleString() + " chunks", 40)}\u2502
5318
- \u2502 Estimated tokens: ${padRight("~" + estimate.estimatedTokens.toLocaleString() + " tokens", 40)}\u2502
5433
+ \u2502 Files to index: ${filesFormatted.padEnd(40)}\u2502
5434
+ \u2502 Total size: ${sizeFormatted.padEnd(40)}\u2502
5435
+ \u2502 Estimated chunks: ${("~" + estimate.estimatedChunks.toLocaleString() + " chunks").padEnd(40)}\u2502
5436
+ \u2502 Estimated tokens: ${("~" + estimate.estimatedTokens.toLocaleString() + " tokens").padEnd(40)}\u2502
5319
5437
  \u2502 \u2502
5320
- \u2502 Provider: ${padRight(estimate.provider, 52)}\u2502
5321
- \u2502 Model: ${padRight(estimate.model, 52)}\u2502
5322
- \u2502 Cost: ${padRight(costFormatted, 52)}\u2502
5438
+ \u2502 Provider: ${estimate.provider.padEnd(52)}\u2502
5439
+ \u2502 Model: ${estimate.model.padEnd(52)}\u2502
5440
+ \u2502 Cost: ${costFormatted.padEnd(52)}\u2502
5323
5441
  \u2502 \u2502
5324
5442
  \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
5325
5443
  `;
@@ -5331,9 +5449,6 @@ function formatBytes(bytes) {
5331
5449
  const i = Math.floor(Math.log(bytes) / Math.log(k));
5332
5450
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
5333
5451
  }
5334
- function padRight(str, length) {
5335
- return str.padEnd(length);
5336
- }
5337
5452
 
5338
5453
  // src/utils/logger.ts
5339
5454
  var LOG_LEVEL_PRIORITY = {
@@ -5400,6 +5515,10 @@ var Logger = class {
5400
5515
  this.logs.shift();
5401
5516
  }
5402
5517
  }
5518
+ withMetrics(fn) {
5519
+ if (!this.config.metrics) return;
5520
+ fn();
5521
+ }
5403
5522
  search(level, message, data) {
5404
5523
  if (this.config.logSearch) {
5405
5524
  this.log(level, "search", message, data);
@@ -5438,89 +5557,107 @@ var Logger = class {
5438
5557
  this.log("debug", "general", message, data);
5439
5558
  }
5440
5559
  recordIndexingStart() {
5441
- if (!this.config.metrics) return;
5442
- this.metrics.indexingStartTime = Date.now();
5560
+ this.withMetrics(() => {
5561
+ this.metrics.indexingStartTime = Date.now();
5562
+ });
5443
5563
  }
5444
5564
  recordIndexingEnd() {
5445
- if (!this.config.metrics) return;
5446
- this.metrics.indexingEndTime = Date.now();
5565
+ this.withMetrics(() => {
5566
+ this.metrics.indexingEndTime = Date.now();
5567
+ });
5447
5568
  }
5448
5569
  recordFilesScanned(count) {
5449
- if (!this.config.metrics) return;
5450
- this.metrics.filesScanned = count;
5570
+ this.withMetrics(() => {
5571
+ this.metrics.filesScanned = count;
5572
+ });
5451
5573
  }
5452
5574
  recordFilesParsed(count) {
5453
- if (!this.config.metrics) return;
5454
- this.metrics.filesParsed = count;
5575
+ this.withMetrics(() => {
5576
+ this.metrics.filesParsed = count;
5577
+ });
5455
5578
  }
5456
5579
  recordParseDuration(durationMs) {
5457
- if (!this.config.metrics) return;
5458
- this.metrics.parseMs = durationMs;
5580
+ this.withMetrics(() => {
5581
+ this.metrics.parseMs = durationMs;
5582
+ });
5459
5583
  }
5460
5584
  recordChunksProcessed(count) {
5461
- if (!this.config.metrics) return;
5462
- this.metrics.chunksProcessed += count;
5585
+ this.withMetrics(() => {
5586
+ this.metrics.chunksProcessed += count;
5587
+ });
5463
5588
  }
5464
5589
  recordChunksEmbedded(count) {
5465
- if (!this.config.metrics) return;
5466
- this.metrics.chunksEmbedded += count;
5590
+ this.withMetrics(() => {
5591
+ this.metrics.chunksEmbedded += count;
5592
+ });
5467
5593
  }
5468
5594
  recordChunksFromCache(count) {
5469
- if (!this.config.metrics) return;
5470
- this.metrics.chunksFromCache += count;
5595
+ this.withMetrics(() => {
5596
+ this.metrics.chunksFromCache += count;
5597
+ });
5471
5598
  }
5472
5599
  recordChunksRemoved(count) {
5473
- if (!this.config.metrics) return;
5474
- this.metrics.chunksRemoved += count;
5600
+ this.withMetrics(() => {
5601
+ this.metrics.chunksRemoved += count;
5602
+ });
5475
5603
  }
5476
5604
  recordEmbeddingApiCall(tokens) {
5477
- if (!this.config.metrics) return;
5478
- this.metrics.embeddingApiCalls++;
5479
- this.metrics.embeddingTokensUsed += tokens;
5605
+ this.withMetrics(() => {
5606
+ this.metrics.embeddingApiCalls++;
5607
+ this.metrics.embeddingTokensUsed += tokens;
5608
+ });
5480
5609
  }
5481
5610
  recordEmbeddingError() {
5482
- if (!this.config.metrics) return;
5483
- this.metrics.embeddingErrors++;
5611
+ this.withMetrics(() => {
5612
+ this.metrics.embeddingErrors++;
5613
+ });
5484
5614
  }
5485
5615
  recordSearch(durationMs, breakdown) {
5486
- if (!this.config.metrics) return;
5487
- this.metrics.searchCount++;
5488
- this.metrics.searchTotalMs += durationMs;
5489
- this.metrics.searchLastMs = durationMs;
5490
- this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5491
- if (breakdown) {
5492
- this.metrics.embeddingCallMs = breakdown.embeddingMs;
5493
- this.metrics.vectorSearchMs = breakdown.vectorMs;
5494
- this.metrics.keywordSearchMs = breakdown.keywordMs;
5495
- this.metrics.fusionMs = breakdown.fusionMs;
5496
- }
5616
+ this.withMetrics(() => {
5617
+ this.metrics.searchCount++;
5618
+ this.metrics.searchTotalMs += durationMs;
5619
+ this.metrics.searchLastMs = durationMs;
5620
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5621
+ if (breakdown) {
5622
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
5623
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
5624
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
5625
+ this.metrics.fusionMs = breakdown.fusionMs;
5626
+ }
5627
+ });
5497
5628
  }
5498
5629
  recordCacheHit() {
5499
- if (!this.config.metrics) return;
5500
- this.metrics.cacheHits++;
5630
+ this.withMetrics(() => {
5631
+ this.metrics.cacheHits++;
5632
+ });
5501
5633
  }
5502
5634
  recordCacheMiss() {
5503
- if (!this.config.metrics) return;
5504
- this.metrics.cacheMisses++;
5635
+ this.withMetrics(() => {
5636
+ this.metrics.cacheMisses++;
5637
+ });
5505
5638
  }
5506
5639
  recordQueryCacheHit() {
5507
- if (!this.config.metrics) return;
5508
- this.metrics.queryCacheHits++;
5640
+ this.withMetrics(() => {
5641
+ this.metrics.queryCacheHits++;
5642
+ });
5509
5643
  }
5510
5644
  recordQueryCacheSimilarHit() {
5511
- if (!this.config.metrics) return;
5512
- this.metrics.queryCacheSimilarHits++;
5645
+ this.withMetrics(() => {
5646
+ this.metrics.queryCacheSimilarHits++;
5647
+ });
5513
5648
  }
5514
5649
  recordQueryCacheMiss() {
5515
- if (!this.config.metrics) return;
5516
- this.metrics.queryCacheMisses++;
5650
+ this.withMetrics(() => {
5651
+ this.metrics.queryCacheMisses++;
5652
+ });
5517
5653
  }
5518
5654
  recordGc(orphans, chunks, embeddings) {
5519
- if (!this.config.metrics) return;
5520
- this.metrics.gcRuns++;
5521
- this.metrics.gcOrphansRemoved += orphans;
5522
- this.metrics.gcChunksRemoved += chunks;
5523
- this.metrics.gcEmbeddingsRemoved += embeddings;
5655
+ this.withMetrics(() => {
5656
+ this.metrics.gcRuns++;
5657
+ this.metrics.gcOrphansRemoved += orphans;
5658
+ this.metrics.gcChunksRemoved += chunks;
5659
+ this.metrics.gcEmbeddingsRemoved += embeddings;
5660
+ });
5524
5661
  }
5525
5662
  getMetrics() {
5526
5663
  return { ...this.metrics };
@@ -5627,7 +5764,7 @@ function initializeLogger(config) {
5627
5764
  }
5628
5765
 
5629
5766
  // src/native/index.ts
5630
- var path7 = __toESM(require("path"), 1);
5767
+ var path11 = __toESM(require("path"), 1);
5631
5768
  var os4 = __toESM(require("os"), 1);
5632
5769
  var module2 = __toESM(require("module"), 1);
5633
5770
  var import_url = require("url");
@@ -5652,19 +5789,19 @@ function getNativeBinding() {
5652
5789
  let currentDir;
5653
5790
  let requireTarget;
5654
5791
  if (typeof import_meta !== "undefined" && import_meta.url) {
5655
- currentDir = path7.dirname((0, import_url.fileURLToPath)(import_meta.url));
5792
+ currentDir = path11.dirname((0, import_url.fileURLToPath)(import_meta.url));
5656
5793
  requireTarget = import_meta.url;
5657
5794
  } else if (typeof __dirname !== "undefined") {
5658
5795
  currentDir = __dirname;
5659
5796
  requireTarget = __filename;
5660
5797
  } else {
5661
5798
  currentDir = process.cwd();
5662
- requireTarget = path7.join(currentDir, "index.js");
5799
+ requireTarget = path11.join(currentDir, "index.js");
5663
5800
  }
5664
5801
  const normalizedDir = currentDir.replace(/\\/g, "/");
5665
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
5666
- const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
5667
- const nativePath = path7.join(packageRoot, "native", bindingName);
5802
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path11.join("src", "native"));
5803
+ const packageRoot = isDevMode ? path11.resolve(currentDir, "../..") : path11.resolve(currentDir, "..");
5804
+ const nativePath = path11.join(packageRoot, "native", bindingName);
5668
5805
  const require2 = module2.createRequire(requireTarget);
5669
5806
  return require2(nativePath);
5670
5807
  }
@@ -6234,7 +6371,6 @@ var Database = class {
6234
6371
  this.throwIfClosed();
6235
6372
  return this.inner.getStats();
6236
6373
  }
6237
- // ── Symbol methods ──────────────────────────────────────────────
6238
6374
  upsertSymbol(symbol) {
6239
6375
  this.throwIfClosed();
6240
6376
  this.inner.upsertSymbol(symbol);
@@ -6264,7 +6400,6 @@ var Database = class {
6264
6400
  this.throwIfClosed();
6265
6401
  return this.inner.deleteSymbolsByFile(filePath);
6266
6402
  }
6267
- // ── Call Edge methods ────────────────────────────────────────────
6268
6403
  upsertCallEdge(edge) {
6269
6404
  this.throwIfClosed();
6270
6405
  this.inner.upsertCallEdge(edge);
@@ -6294,7 +6429,6 @@ var Database = class {
6294
6429
  this.throwIfClosed();
6295
6430
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6296
6431
  }
6297
- // ── Branch Symbol methods ────────────────────────────────────────
6298
6432
  addSymbolsToBranch(branch, symbolIds) {
6299
6433
  this.throwIfClosed();
6300
6434
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6327,7 +6461,6 @@ var Database = class {
6327
6461
  if (symbolIds.length === 0) return 0;
6328
6462
  return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6329
6463
  }
6330
- // ── GC methods for symbols/edges ─────────────────────────────────
6331
6464
  gcOrphanSymbols() {
6332
6465
  this.throwIfClosed();
6333
6466
  return this.inner.gcOrphanSymbols();
@@ -6339,7 +6472,7 @@ var Database = class {
6339
6472
  };
6340
6473
 
6341
6474
  // src/indexer/index.ts
6342
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6475
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6343
6476
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
6344
6477
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6345
6478
  "function_declaration",
@@ -6366,7 +6499,15 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6366
6499
  "trigger_declaration",
6367
6500
  "test_declaration",
6368
6501
  "struct_declaration",
6369
- "union_declaration"
6502
+ "union_declaration",
6503
+ // GDScript declarations whose names participate in the call graph.
6504
+ // `function_definition` and `class_definition` are already in the set
6505
+ // above (shared with Python/C/Bash and Python, respectively).
6506
+ "constructor_definition",
6507
+ "enum_definition",
6508
+ "signal_statement",
6509
+ "const_statement",
6510
+ "class_name_statement"
6370
6511
  ]);
6371
6512
  function float32ArrayToBuffer(arr) {
6372
6513
  const float32 = new Float32Array(arr);
@@ -6568,9 +6709,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6568
6709
  return true;
6569
6710
  }
6570
6711
  function isPathWithinRoot(filePath, rootPath) {
6571
- const normalizedFilePath = path8.resolve(filePath);
6572
- const normalizedRoot = path8.resolve(rootPath);
6573
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path8.sep}`);
6712
+ const normalizedFilePath = path12.resolve(filePath);
6713
+ const normalizedRoot = path12.resolve(rootPath);
6714
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6574
6715
  }
6575
6716
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6576
6717
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7623,22 +7764,28 @@ var Indexer = class {
7623
7764
  this.projectRoot = projectRoot;
7624
7765
  this.config = config;
7625
7766
  this.indexPath = this.getIndexPath();
7626
- this.fileHashCachePath = path8.join(this.indexPath, "file-hashes.json");
7627
- this.failedBatchesPath = path8.join(this.indexPath, "failed-batches.json");
7628
- this.indexingLockPath = path8.join(this.indexPath, "indexing.lock");
7767
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7768
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7769
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7629
7770
  this.logger = initializeLogger(config.debug);
7630
7771
  }
7631
7772
  getIndexPath() {
7632
7773
  return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7633
7774
  }
7634
7775
  loadFileHashCache() {
7776
+ if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
7777
+ return;
7778
+ }
7635
7779
  try {
7636
- if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
7637
- const data = (0, import_fs6.readFileSync)(this.fileHashCachePath, "utf-8");
7638
- const parsed = JSON.parse(data);
7639
- this.fileHashCache = new Map(Object.entries(parsed));
7640
- }
7641
- } catch {
7780
+ const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
7781
+ const parsed = JSON.parse(data);
7782
+ this.fileHashCache = new Map(Object.entries(parsed));
7783
+ } catch (error) {
7784
+ const message = error instanceof Error ? error.message : String(error);
7785
+ this.logger.warn("Failed to load file hash cache, resetting cache state", {
7786
+ fileHashCachePath: this.fileHashCachePath,
7787
+ error: message
7788
+ });
7642
7789
  this.fileHashCache = /* @__PURE__ */ new Map();
7643
7790
  }
7644
7791
  }
@@ -7651,14 +7798,14 @@ var Indexer = class {
7651
7798
  }
7652
7799
  atomicWriteSync(targetPath, data) {
7653
7800
  const tempPath = `${targetPath}.tmp`;
7654
- (0, import_fs6.mkdirSync)(path8.dirname(targetPath), { recursive: true });
7655
- (0, import_fs6.writeFileSync)(tempPath, data);
7656
- (0, import_fs6.renameSync)(tempPath, targetPath);
7801
+ (0, import_fs7.mkdirSync)(path12.dirname(targetPath), { recursive: true });
7802
+ (0, import_fs7.writeFileSync)(tempPath, data);
7803
+ (0, import_fs7.renameSync)(tempPath, targetPath);
7657
7804
  }
7658
7805
  getScopedRoots() {
7659
- const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7806
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7660
7807
  for (const kbRoot of this.config.knowledgeBases) {
7661
- roots.add(path8.resolve(this.projectRoot, kbRoot));
7808
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
7662
7809
  }
7663
7810
  return Array.from(roots);
7664
7811
  }
@@ -7667,22 +7814,22 @@ var Indexer = class {
7667
7814
  if (this.config.scope !== "global") {
7668
7815
  return branchName;
7669
7816
  }
7670
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7817
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7671
7818
  return `${projectHash}:${branchName}`;
7672
7819
  }
7673
7820
  getLegacyBranchCatalogKey() {
7674
7821
  return this.currentBranch || "default";
7675
7822
  }
7676
7823
  getLegacyMigrationMetadataKey() {
7677
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7824
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7678
7825
  return `index.globalBranchMigration.${projectHash}`;
7679
7826
  }
7680
7827
  getProjectEmbeddingStrategyMetadataKey() {
7681
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7828
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7682
7829
  return `index.embeddingStrategyVersion.${projectHash}`;
7683
7830
  }
7684
7831
  getProjectForceReembedMetadataKey() {
7685
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7832
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7686
7833
  return `index.forceReembed.${projectHash}`;
7687
7834
  }
7688
7835
  hasProjectForceReembedPending() {
@@ -7776,7 +7923,7 @@ var Indexer = class {
7776
7923
  if (!this.database) {
7777
7924
  return { chunkIds, symbolIds };
7778
7925
  }
7779
- const projectRootPath = path8.resolve(this.projectRoot);
7926
+ const projectRootPath = path12.resolve(this.projectRoot);
7780
7927
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7781
7928
  ...Array.from(this.fileHashCache.keys()).filter(
7782
7929
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7799,7 +7946,7 @@ var Indexer = class {
7799
7946
  if (this.config.scope !== "global") {
7800
7947
  return this.getBranchCatalogCleanupKeys();
7801
7948
  }
7802
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7949
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7803
7950
  const keys = /* @__PURE__ */ new Set();
7804
7951
  const projectChunkIdSet = new Set(projectChunkIds);
7805
7952
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7883,7 +8030,7 @@ var Indexer = class {
7883
8030
  if (!this.database || this.config.scope !== "global") {
7884
8031
  return false;
7885
8032
  }
7886
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
8033
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7887
8034
  const roots = this.getScopedRoots();
7888
8035
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7889
8036
  return this.database.getAllBranches().some(
@@ -7917,7 +8064,7 @@ var Indexer = class {
7917
8064
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7918
8065
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7919
8066
  ]);
7920
- const projectRootPath = path8.resolve(this.projectRoot);
8067
+ const projectRootPath = path12.resolve(this.projectRoot);
7921
8068
  const projectLocalFilePaths = new Set(
7922
8069
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7923
8070
  );
@@ -7986,24 +8133,24 @@ var Indexer = class {
7986
8133
  };
7987
8134
  }
7988
8135
  checkForInterruptedIndexing() {
7989
- return (0, import_fs6.existsSync)(this.indexingLockPath);
8136
+ return (0, import_fs7.existsSync)(this.indexingLockPath);
7990
8137
  }
7991
8138
  acquireIndexingLock() {
7992
8139
  const lockData = {
7993
8140
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7994
8141
  pid: process.pid
7995
8142
  };
7996
- (0, import_fs6.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
8143
+ (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
7997
8144
  }
7998
8145
  releaseIndexingLock() {
7999
- if ((0, import_fs6.existsSync)(this.indexingLockPath)) {
8000
- (0, import_fs6.unlinkSync)(this.indexingLockPath);
8146
+ if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
8147
+ (0, import_fs7.unlinkSync)(this.indexingLockPath);
8001
8148
  }
8002
8149
  }
8003
8150
  async recoverFromInterruptedIndexing() {
8004
8151
  this.logger.warn("Detected interrupted indexing session, recovering...");
8005
- if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
8006
- (0, import_fs6.unlinkSync)(this.fileHashCachePath);
8152
+ if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
8153
+ (0, import_fs7.unlinkSync)(this.fileHashCachePath);
8007
8154
  }
8008
8155
  await this.healthCheck();
8009
8156
  this.releaseIndexingLock();
@@ -8012,15 +8159,20 @@ var Indexer = class {
8012
8159
  loadFailedBatches(maxChunkTokens) {
8013
8160
  try {
8014
8161
  return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8015
- } catch {
8162
+ } catch (error) {
8163
+ const message = error instanceof Error ? error.message : String(error);
8164
+ this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
8165
+ failedBatchesPath: this.failedBatchesPath,
8166
+ error: message
8167
+ });
8016
8168
  return [];
8017
8169
  }
8018
8170
  }
8019
8171
  loadSerializedFailedBatches() {
8020
- if (!(0, import_fs6.existsSync)(this.failedBatchesPath)) {
8172
+ if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
8021
8173
  return [];
8022
8174
  }
8023
- const data = (0, import_fs6.readFileSync)(this.failedBatchesPath, "utf-8");
8175
+ const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
8024
8176
  const parsed = JSON.parse(data);
8025
8177
  return parsed.map((batch) => {
8026
8178
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -8037,15 +8189,15 @@ var Indexer = class {
8037
8189
  }
8038
8190
  saveFailedBatches(batches) {
8039
8191
  if (batches.length === 0) {
8040
- if ((0, import_fs6.existsSync)(this.failedBatchesPath)) {
8192
+ if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
8041
8193
  try {
8042
- (0, import_fs6.unlinkSync)(this.failedBatchesPath);
8194
+ (0, import_fs7.unlinkSync)(this.failedBatchesPath);
8043
8195
  } catch {
8044
8196
  }
8045
8197
  }
8046
8198
  return;
8047
8199
  }
8048
- (0, import_fs6.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8200
+ (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8049
8201
  }
8050
8202
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8051
8203
  const retryableById = /* @__PURE__ */ new Map();
@@ -8225,7 +8377,7 @@ var Indexer = class {
8225
8377
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
8226
8378
  parts.push(`intent_hint: ${intent}`);
8227
8379
  try {
8228
- const fileContent = await import_fs6.promises.readFile(candidate.metadata.filePath, "utf-8");
8380
+ const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
8229
8381
  const lines = fileContent.split("\n");
8230
8382
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
8231
8383
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -8270,26 +8422,26 @@ var Indexer = class {
8270
8422
  });
8271
8423
  }
8272
8424
  }
8273
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8425
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8274
8426
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8275
- const storePath = path8.join(this.indexPath, "vectors");
8427
+ const storePath = path12.join(this.indexPath, "vectors");
8276
8428
  this.store = new VectorStore(storePath, dimensions);
8277
- const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8278
- if ((0, import_fs6.existsSync)(indexFilePath)) {
8429
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8430
+ if ((0, import_fs7.existsSync)(indexFilePath)) {
8279
8431
  this.store.load();
8280
8432
  }
8281
- const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
8433
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8282
8434
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8283
8435
  try {
8284
8436
  this.invertedIndex.load();
8285
8437
  } catch {
8286
- if ((0, import_fs6.existsSync)(invertedIndexPath)) {
8287
- await import_fs6.promises.unlink(invertedIndexPath);
8438
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
8439
+ await import_fs7.promises.unlink(invertedIndexPath);
8288
8440
  }
8289
8441
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8290
8442
  }
8291
- const dbPath = path8.join(this.indexPath, "codebase.db");
8292
- let dbIsNew = !(0, import_fs6.existsSync)(dbPath);
8443
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8444
+ let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
8293
8445
  try {
8294
8446
  this.database = new Database(dbPath);
8295
8447
  } catch (error) {
@@ -8369,7 +8521,7 @@ var Indexer = class {
8369
8521
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8370
8522
  return {
8371
8523
  resetCorruptedIndex: true,
8372
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8524
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8373
8525
  };
8374
8526
  }
8375
8527
  throw error;
@@ -8384,7 +8536,7 @@ var Indexer = class {
8384
8536
  return;
8385
8537
  }
8386
8538
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8387
- const storeBasePath = path8.join(this.indexPath, "vectors");
8539
+ const storeBasePath = path12.join(this.indexPath, "vectors");
8388
8540
  const storeIndexPath = `${storeBasePath}.usearch`;
8389
8541
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8390
8542
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8393,19 +8545,19 @@ var Indexer = class {
8393
8545
  let backedUpMetadata = false;
8394
8546
  let rebuiltCount = 0;
8395
8547
  let skippedCount = 0;
8396
- if ((0, import_fs6.existsSync)(backupIndexPath)) {
8397
- (0, import_fs6.unlinkSync)(backupIndexPath);
8548
+ if ((0, import_fs7.existsSync)(backupIndexPath)) {
8549
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8398
8550
  }
8399
- if ((0, import_fs6.existsSync)(backupMetadataPath)) {
8400
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8551
+ if ((0, import_fs7.existsSync)(backupMetadataPath)) {
8552
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8401
8553
  }
8402
8554
  try {
8403
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8404
- (0, import_fs6.renameSync)(storeIndexPath, backupIndexPath);
8555
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8556
+ (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
8405
8557
  backedUpIndex = true;
8406
8558
  }
8407
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8408
- (0, import_fs6.renameSync)(storeMetadataPath, backupMetadataPath);
8559
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8560
+ (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
8409
8561
  backedUpMetadata = true;
8410
8562
  }
8411
8563
  store.clear();
@@ -8425,11 +8577,11 @@ var Indexer = class {
8425
8577
  rebuiltCount += 1;
8426
8578
  }
8427
8579
  store.save();
8428
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8429
- (0, import_fs6.unlinkSync)(backupIndexPath);
8580
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8581
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8430
8582
  }
8431
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8432
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8583
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8584
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8433
8585
  }
8434
8586
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8435
8587
  excludedChunks: excludedSet.size,
@@ -8441,17 +8593,17 @@ var Indexer = class {
8441
8593
  store.clear();
8442
8594
  } catch {
8443
8595
  }
8444
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8445
- (0, import_fs6.unlinkSync)(storeIndexPath);
8596
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8597
+ (0, import_fs7.unlinkSync)(storeIndexPath);
8446
8598
  }
8447
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8448
- (0, import_fs6.unlinkSync)(storeMetadataPath);
8599
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8600
+ (0, import_fs7.unlinkSync)(storeMetadataPath);
8449
8601
  }
8450
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8451
- (0, import_fs6.renameSync)(backupIndexPath, storeIndexPath);
8602
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8603
+ (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
8452
8604
  }
8453
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8454
- (0, import_fs6.renameSync)(backupMetadataPath, storeMetadataPath);
8605
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8606
+ (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
8455
8607
  }
8456
8608
  if (backedUpIndex || backedUpMetadata) {
8457
8609
  store.load();
@@ -8469,7 +8621,7 @@ var Indexer = class {
8469
8621
  if (!isSqliteCorruptionError(error)) {
8470
8622
  return false;
8471
8623
  }
8472
- const dbPath = path8.join(this.indexPath, "codebase.db");
8624
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8473
8625
  const warning = this.getCorruptedIndexWarning(dbPath);
8474
8626
  const errorMessage = getErrorMessage(error);
8475
8627
  if (this.config.scope === "global") {
@@ -8492,23 +8644,23 @@ var Indexer = class {
8492
8644
  this.indexCompatibility = null;
8493
8645
  this.fileHashCache.clear();
8494
8646
  const resetPaths = [
8495
- path8.join(this.indexPath, "codebase.db"),
8496
- path8.join(this.indexPath, "codebase.db-shm"),
8497
- path8.join(this.indexPath, "codebase.db-wal"),
8498
- path8.join(this.indexPath, "vectors.usearch"),
8499
- path8.join(this.indexPath, "inverted-index.json"),
8500
- path8.join(this.indexPath, "file-hashes.json"),
8501
- path8.join(this.indexPath, "failed-batches.json"),
8502
- path8.join(this.indexPath, "indexing.lock"),
8503
- path8.join(this.indexPath, "vectors")
8647
+ path12.join(this.indexPath, "codebase.db"),
8648
+ path12.join(this.indexPath, "codebase.db-shm"),
8649
+ path12.join(this.indexPath, "codebase.db-wal"),
8650
+ path12.join(this.indexPath, "vectors.usearch"),
8651
+ path12.join(this.indexPath, "inverted-index.json"),
8652
+ path12.join(this.indexPath, "file-hashes.json"),
8653
+ path12.join(this.indexPath, "failed-batches.json"),
8654
+ path12.join(this.indexPath, "indexing.lock"),
8655
+ path12.join(this.indexPath, "vectors")
8504
8656
  ];
8505
8657
  await Promise.all(resetPaths.map(async (targetPath) => {
8506
8658
  try {
8507
- await import_fs6.promises.rm(targetPath, { recursive: true, force: true });
8659
+ await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
8508
8660
  } catch {
8509
8661
  }
8510
8662
  }));
8511
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8663
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8512
8664
  return true;
8513
8665
  }
8514
8666
  migrateFromLegacyIndex() {
@@ -8713,7 +8865,7 @@ var Indexer = class {
8713
8865
  unchangedFilePaths.add(f.path);
8714
8866
  this.logger.recordCacheHit();
8715
8867
  } else {
8716
- const content = await import_fs6.promises.readFile(f.path, "utf-8");
8868
+ const content = await import_fs7.promises.readFile(f.path, "utf-8");
8717
8869
  changedFiles.push({ path: f.path, content, hash: currentHash });
8718
8870
  this.logger.recordCacheMiss();
8719
8871
  }
@@ -8765,7 +8917,7 @@ var Indexer = class {
8765
8917
  for (const parsed of parsedFiles) {
8766
8918
  currentFilePaths.add(parsed.path);
8767
8919
  if (parsed.chunks.length === 0) {
8768
- const relativePath = path8.relative(this.projectRoot, parsed.path);
8920
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
8769
8921
  stats.parseFailures.push(relativePath);
8770
8922
  }
8771
8923
  let fileChunkCount = 0;
@@ -9048,7 +9200,7 @@ var Indexer = class {
9048
9200
  for (const requestBatch of requestBatches) {
9049
9201
  queue.add(async () => {
9050
9202
  if (rateLimitBackoffMs > 0) {
9051
- await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
9203
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9052
9204
  }
9053
9205
  try {
9054
9206
  const result = await pRetry(
@@ -9497,7 +9649,7 @@ var Indexer = class {
9497
9649
  let contextEndLine = r.metadata.endLine;
9498
9650
  if (!metadataOnly && this.config.search.includeContext) {
9499
9651
  try {
9500
- const fileContent = await import_fs6.promises.readFile(
9652
+ const fileContent = await import_fs7.promises.readFile(
9501
9653
  r.metadata.filePath,
9502
9654
  "utf-8"
9503
9655
  );
@@ -9609,8 +9761,8 @@ var Indexer = class {
9609
9761
  this.indexCompatibility = compatibility;
9610
9762
  return;
9611
9763
  }
9612
- const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9613
- if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9764
+ const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9765
+ if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9614
9766
  throw new Error(
9615
9767
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9616
9768
  );
@@ -9649,7 +9801,7 @@ var Indexer = class {
9649
9801
  const removedChunkKeys = [];
9650
9802
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9651
9803
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9652
- if (!(0, import_fs6.existsSync)(filePath)) {
9804
+ if (!(0, import_fs7.existsSync)(filePath)) {
9653
9805
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9654
9806
  for (const key of chunkKeys) {
9655
9807
  removedChunkKeys.push(key);
@@ -9698,7 +9850,7 @@ var Indexer = class {
9698
9850
  gcOrphanSymbols: 0,
9699
9851
  gcOrphanCallEdges: 0,
9700
9852
  resetCorruptedIndex: true,
9701
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9853
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9702
9854
  };
9703
9855
  }
9704
9856
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10001,7 +10153,7 @@ var Indexer = class {
10001
10153
  let content = "";
10002
10154
  if (this.config.search.includeContext) {
10003
10155
  try {
10004
- const fileContent = await import_fs6.promises.readFile(
10156
+ const fileContent = await import_fs7.promises.readFile(
10005
10157
  r.metadata.filePath,
10006
10158
  "utf-8"
10007
10159
  );
@@ -10250,12 +10402,15 @@ function formatLogs(logs) {
10250
10402
  return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10251
10403
  }).join("\n");
10252
10404
  }
10405
+ function formatResultHeader(result, index) {
10406
+ 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}`;
10407
+ }
10253
10408
  function formatDefinitionLookup(results, query) {
10254
10409
  if (results.length === 0) {
10255
10410
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
10256
10411
  }
10257
10412
  const formatted = results.map((r, idx) => {
10258
- 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}`;
10413
+ const header = formatResultHeader(r, idx);
10259
10414
  return `${header} (score: ${r.score.toFixed(2)})
10260
10415
  \`\`\`
10261
10416
  ${truncateContent(r.content)}
@@ -10265,7 +10420,7 @@ ${truncateContent(r.content)}
10265
10420
  }
10266
10421
  function formatSearchResults(results, scoreFormat = "similarity") {
10267
10422
  const formatted = results.map((r, idx) => {
10268
- 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}`;
10423
+ const header = formatResultHeader(r, idx);
10269
10424
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
10270
10425
  return `${header} ${scoreLabel}
10271
10426
  \`\`\`
@@ -10275,105 +10430,84 @@ ${truncateContent(r.content)}
10275
10430
  return formatted.join("\n\n");
10276
10431
  }
10277
10432
 
10278
- // src/tools/index.ts
10279
- var import_fs7 = require("fs");
10280
- var path9 = __toESM(require("path"), 1);
10281
- var z = import_plugin.tool.schema;
10282
- var sharedIndexer = null;
10283
- var sharedProjectRoot = "";
10284
- function initializeTools(projectRoot, config) {
10285
- sharedProjectRoot = projectRoot;
10286
- sharedIndexer = new Indexer(projectRoot, config);
10287
- }
10288
- function getSharedIndexer() {
10289
- return getIndexer();
10290
- }
10291
- function refreshIndexerFromConfig() {
10292
- if (!sharedProjectRoot) {
10293
- throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10294
- }
10295
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10296
- }
10297
- function shouldForceLocalizeProjectIndex() {
10298
- const currentConfig = parseConfig(loadRuntimeConfig());
10299
- if (currentConfig.scope !== "project") {
10300
- return false;
10301
- }
10302
- const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10303
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10304
- if (!mainRepoRoot) {
10305
- return false;
10306
- }
10307
- const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10308
- return !(0, import_fs7.existsSync)(localIndexPath) && (0, import_fs7.existsSync)(inheritedIndexPath);
10309
- }
10310
- function getIndexer() {
10311
- if (!sharedIndexer) {
10312
- throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10313
- }
10314
- return sharedIndexer;
10315
- }
10316
- function getConfigPath() {
10317
- return resolveWritableProjectConfigPath(sharedProjectRoot);
10318
- }
10319
- function normalizeConfigPathValue(value, baseDir) {
10433
+ // src/tools/knowledge-base-paths.ts
10434
+ var path13 = __toESM(require("path"), 1);
10435
+ function resolveConfigPathValue(value, baseDir) {
10320
10436
  const trimmed = value.trim();
10321
10437
  if (!trimmed) {
10322
10438
  return trimmed;
10323
10439
  }
10324
- const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10325
- return path9.normalize(absolutePath);
10440
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10441
+ return path13.normalize(absolutePath);
10326
10442
  }
10327
10443
  function serializeConfigPathValue(value, baseDir) {
10328
10444
  const trimmed = value.trim();
10329
10445
  if (!trimmed) {
10330
10446
  return trimmed;
10331
10447
  }
10332
- const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10333
- if (!path9.isAbsolute(trimmed)) {
10334
- return normalizeRelativePath(path9.normalize(trimmed));
10448
+ if (!path13.isAbsolute(trimmed)) {
10449
+ return normalizePathSeparators(path13.normalize(trimmed));
10335
10450
  }
10336
- const relativePath = path9.relative(baseDir, trimmed);
10337
- if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10338
- return normalizeRelativePath(path9.normalize(relativePath || "."));
10451
+ const relativePath = path13.relative(baseDir, trimmed);
10452
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10453
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
10339
10454
  }
10340
- return path9.normalize(trimmed);
10455
+ return path13.normalize(trimmed);
10456
+ }
10457
+ function resolveKnowledgeBasePath(value, projectRoot) {
10458
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10459
+ }
10460
+ function normalizeKnowledgeBasePath2(value, projectRoot) {
10461
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10462
+ }
10463
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10464
+ const normalizedInput = path13.normalize(inputPath);
10465
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10466
+ }
10467
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10468
+ const normalizedInput = path13.normalize(inputPath);
10469
+ return knowledgeBases.findIndex(
10470
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10471
+ );
10341
10472
  }
10342
- function normalizeKnowledgeBasePaths(config) {
10473
+
10474
+ // src/tools/index.ts
10475
+ var import_fs9 = require("fs");
10476
+ var path15 = __toESM(require("path"), 1);
10477
+
10478
+ // src/tools/config-state.ts
10479
+ var import_fs8 = require("fs");
10480
+ var path14 = __toESM(require("path"), 1);
10481
+ function normalizeKnowledgeBasePaths(config, projectRoot) {
10343
10482
  const normalized = { ...config };
10344
10483
  if (Array.isArray(normalized.knowledgeBases)) {
10345
- normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10346
- return normalizeConfigPathValue(kb, sharedProjectRoot);
10347
- });
10484
+ normalized.knowledgeBases = normalized.knowledgeBases.map(
10485
+ (kb) => resolveConfigPathValue(kb, projectRoot)
10486
+ );
10348
10487
  }
10349
10488
  return normalized;
10350
10489
  }
10351
- function loadRuntimeConfig() {
10352
- const rawConfig = loadMergedConfig(sharedProjectRoot);
10353
- const config = {};
10354
- if (rawConfig && typeof rawConfig === "object") {
10355
- for (const key of Object.keys(rawConfig)) {
10356
- config[key] = rawConfig[key];
10357
- }
10490
+ function toConfigRecord(rawConfig) {
10491
+ if (!rawConfig || typeof rawConfig !== "object") {
10492
+ return {};
10358
10493
  }
10359
- return normalizeKnowledgeBasePaths(config);
10494
+ return { ...rawConfig };
10360
10495
  }
10361
- function loadEditableConfig() {
10362
- const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10363
- const config = {};
10364
- if (rawConfig && typeof rawConfig === "object") {
10365
- for (const key of Object.keys(rawConfig)) {
10366
- config[key] = rawConfig[key];
10367
- }
10368
- }
10369
- return normalizeKnowledgeBasePaths(config);
10496
+ function getConfigPath(projectRoot) {
10497
+ return resolveWritableProjectConfigPath(projectRoot);
10498
+ }
10499
+ function loadRuntimeConfig(projectRoot) {
10500
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot)), projectRoot);
10501
+ }
10502
+ function loadEditableConfig(projectRoot) {
10503
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot)), projectRoot);
10370
10504
  }
10371
- function saveConfig(config) {
10372
- const configPath = getConfigPath();
10373
- const configDir = path9.dirname(configPath);
10374
- const configBaseDir = path9.dirname(configDir);
10375
- if (!(0, import_fs7.existsSync)(configDir)) {
10376
- (0, import_fs7.mkdirSync)(configDir, { recursive: true });
10505
+ function saveConfig(projectRoot, config) {
10506
+ const configPath = getConfigPath(projectRoot);
10507
+ const configDir = path14.dirname(configPath);
10508
+ const configBaseDir = path14.dirname(configDir);
10509
+ if (!(0, import_fs8.existsSync)(configDir)) {
10510
+ (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10377
10511
  }
10378
10512
  const serializableConfig = { ...config };
10379
10513
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -10381,7 +10515,54 @@ function saveConfig(config) {
10381
10515
  (kb) => serializeConfigPathValue(kb, configBaseDir)
10382
10516
  );
10383
10517
  }
10384
- (0, import_fs7.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10518
+ (0, import_fs8.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10519
+ }
10520
+
10521
+ // src/tools/index.ts
10522
+ var os5 = __toESM(require("os"), 1);
10523
+ function ensureStringArray(value) {
10524
+ return Array.isArray(value) ? value : [];
10525
+ }
10526
+ var z = import_plugin.tool.schema;
10527
+ var indexerMap = /* @__PURE__ */ new Map();
10528
+ var defaultProjectRoot = "";
10529
+ function initializeTools(projectRoot, config) {
10530
+ defaultProjectRoot = projectRoot;
10531
+ const indexer = new Indexer(projectRoot, config);
10532
+ indexerMap.set(projectRoot, indexer);
10533
+ }
10534
+ function refreshIndexerForDirectory(projectRoot) {
10535
+ if (!projectRoot) {
10536
+ throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10537
+ }
10538
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10539
+ indexerMap.set(projectRoot, indexer);
10540
+ }
10541
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10542
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10543
+ if (currentConfig.scope !== "project") {
10544
+ return false;
10545
+ }
10546
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10547
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10548
+ if (!mainRepoRoot) {
10549
+ return false;
10550
+ }
10551
+ const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10552
+ return !(0, import_fs9.existsSync)(localIndexPath) && (0, import_fs9.existsSync)(inheritedIndexPath);
10553
+ }
10554
+ function getIndexerForProject(directory) {
10555
+ const projectRoot = directory || defaultProjectRoot;
10556
+ if (!projectRoot) {
10557
+ throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10558
+ }
10559
+ let indexer = indexerMap.get(projectRoot);
10560
+ if (!indexer) {
10561
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10562
+ indexer = new Indexer(projectRoot, config);
10563
+ indexerMap.set(projectRoot, indexer);
10564
+ }
10565
+ return indexer;
10385
10566
  }
10386
10567
  var codebase_peek = (0, import_plugin.tool)({
10387
10568
  description: "Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Use this first to find WHERE code is, then use Read tool to examine specific files. Saves tokens by not returning full code blocks. Best for: discovery, navigation, finding multiple related locations.",
@@ -10392,8 +10573,8 @@ var codebase_peek = (0, import_plugin.tool)({
10392
10573
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10393
10574
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10394
10575
  },
10395
- async execute(args) {
10396
- const indexer = getIndexer();
10576
+ async execute(args, context) {
10577
+ const indexer = getIndexerForProject(context?.worktree);
10397
10578
  const results = await indexer.search(args.query, args.limit ?? 10, {
10398
10579
  fileType: args.fileType,
10399
10580
  directory: args.directory,
@@ -10411,16 +10592,17 @@ var index_codebase = (0, import_plugin.tool)({
10411
10592
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10412
10593
  },
10413
10594
  async execute(args, context) {
10414
- let indexer = getIndexer();
10595
+ const projectRoot = context?.worktree || defaultProjectRoot;
10596
+ let indexer = getIndexerForProject(projectRoot);
10415
10597
  if (args.estimateOnly) {
10416
10598
  const estimate = await indexer.estimateCost();
10417
10599
  return formatCostEstimate(estimate);
10418
10600
  }
10419
10601
  if (args.force) {
10420
- if (shouldForceLocalizeProjectIndex()) {
10421
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10422
- refreshIndexerFromConfig();
10423
- indexer = getIndexer();
10602
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10603
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10604
+ refreshIndexerForDirectory(projectRoot);
10605
+ indexer = getIndexerForProject(projectRoot);
10424
10606
  }
10425
10607
  await indexer.clearIndex();
10426
10608
  }
@@ -10443,8 +10625,8 @@ var index_codebase = (0, import_plugin.tool)({
10443
10625
  var index_status = (0, import_plugin.tool)({
10444
10626
  description: "Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
10445
10627
  args: {},
10446
- async execute() {
10447
- const indexer = getIndexer();
10628
+ async execute(_args, context) {
10629
+ const indexer = getIndexerForProject(context?.worktree);
10448
10630
  const status = await indexer.getStatus();
10449
10631
  return formatStatus(status);
10450
10632
  }
@@ -10452,8 +10634,8 @@ var index_status = (0, import_plugin.tool)({
10452
10634
  var index_health_check = (0, import_plugin.tool)({
10453
10635
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10454
10636
  args: {},
10455
- async execute() {
10456
- const indexer = getIndexer();
10637
+ async execute(_args, context) {
10638
+ const indexer = getIndexerForProject(context?.worktree);
10457
10639
  const result = await indexer.healthCheck();
10458
10640
  return formatHealthCheck(result);
10459
10641
  }
@@ -10461,8 +10643,8 @@ var index_health_check = (0, import_plugin.tool)({
10461
10643
  var index_metrics = (0, import_plugin.tool)({
10462
10644
  description: "Get metrics and performance statistics for the codebase index. Shows indexing stats, search timings, cache hit rates, and API usage. Requires debug.enabled=true and debug.metrics=true in config.",
10463
10645
  args: {},
10464
- async execute() {
10465
- const indexer = getIndexer();
10646
+ async execute(_args, context) {
10647
+ const indexer = getIndexerForProject(context?.worktree);
10466
10648
  const logger = indexer.getLogger();
10467
10649
  if (!logger.isEnabled()) {
10468
10650
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true,\n "metrics": true\n }\n}\n```';
@@ -10480,8 +10662,8 @@ var index_logs = (0, import_plugin.tool)({
10480
10662
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10481
10663
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10482
10664
  },
10483
- async execute(args) {
10484
- const indexer = getIndexer();
10665
+ async execute(args, context) {
10666
+ const indexer = getIndexerForProject(context?.worktree);
10485
10667
  const logger = indexer.getLogger();
10486
10668
  if (!logger.isEnabled()) {
10487
10669
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10507,8 +10689,8 @@ var find_similar = (0, import_plugin.tool)({
10507
10689
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10508
10690
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10509
10691
  },
10510
- async execute(args) {
10511
- const indexer = getIndexer();
10692
+ async execute(args, context) {
10693
+ const indexer = getIndexerForProject(context?.worktree);
10512
10694
  const results = await indexer.findSimilar(args.code, args.limit, {
10513
10695
  fileType: args.fileType,
10514
10696
  directory: args.directory,
@@ -10531,8 +10713,8 @@ var codebase_search = (0, import_plugin.tool)({
10531
10713
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10532
10714
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10533
10715
  },
10534
- async execute(args) {
10535
- const indexer = getIndexer();
10716
+ async execute(args, context) {
10717
+ const indexer = getIndexerForProject(context?.worktree);
10536
10718
  const results = await indexer.search(args.query, args.limit ?? 5, {
10537
10719
  fileType: args.fileType,
10538
10720
  directory: args.directory,
@@ -10553,8 +10735,8 @@ var implementation_lookup = (0, import_plugin.tool)({
10553
10735
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10554
10736
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10555
10737
  },
10556
- async execute(args) {
10557
- const indexer = getIndexer();
10738
+ async execute(args, context) {
10739
+ const indexer = getIndexerForProject(context?.worktree);
10558
10740
  const results = await indexer.search(args.query, args.limit ?? 5, {
10559
10741
  fileType: args.fileType,
10560
10742
  directory: args.directory,
@@ -10570,8 +10752,8 @@ var call_graph = (0, import_plugin.tool)({
10570
10752
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10571
10753
  symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10572
10754
  },
10573
- async execute(args) {
10574
- const indexer = getIndexer();
10755
+ async execute(args, context) {
10756
+ const indexer = getIndexerForProject(context?.worktree);
10575
10757
  if (args.direction === "callees") {
10576
10758
  if (!args.symbolId) {
10577
10759
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
@@ -10600,38 +10782,67 @@ var add_knowledge_base = (0, import_plugin.tool)({
10600
10782
  args: {
10601
10783
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10602
10784
  },
10603
- async execute(args) {
10785
+ async execute(args, context) {
10786
+ const projectRoot = context?.worktree || defaultProjectRoot;
10604
10787
  const inputPath = args.path.trim();
10605
- const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10606
- if (!(0, import_fs7.existsSync)(resolvedPath)) {
10607
- return `Error: Directory does not exist: ${resolvedPath}`;
10788
+ const normalizedPath = path15.resolve(
10789
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10790
+ );
10791
+ if (!(0, import_fs9.existsSync)(normalizedPath)) {
10792
+ return `Error: Directory does not exist: ${normalizedPath}`;
10793
+ }
10794
+ let realPath;
10795
+ try {
10796
+ realPath = (0, import_fs9.realpathSync)(normalizedPath);
10797
+ } catch {
10798
+ return `Error: Cannot resolve path: ${normalizedPath}`;
10799
+ }
10800
+ const blockedPrefixes = [
10801
+ "/etc",
10802
+ "/proc",
10803
+ "/sys",
10804
+ "/dev",
10805
+ "/boot",
10806
+ "/root",
10807
+ "/var/run",
10808
+ "/var/log"
10809
+ ];
10810
+ const homeDir = os5.homedir();
10811
+ const sensitiveDotDirs = [".ssh", ".gnupg", ".aws", ".config/gcloud", ".docker", ".kube"];
10812
+ for (const prefix of blockedPrefixes) {
10813
+ if (realPath === prefix || realPath.startsWith(prefix + "/")) {
10814
+ return `Error: Adding system directory as knowledge base is not allowed: ${normalizedPath}`;
10815
+ }
10816
+ }
10817
+ for (const dotDir of sensitiveDotDirs) {
10818
+ const sensitiveDir = path15.join(homeDir, dotDir);
10819
+ if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10820
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10821
+ }
10608
10822
  }
10609
10823
  try {
10610
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10824
+ const stat4 = (0, import_fs9.statSync)(normalizedPath);
10611
10825
  if (!stat4.isDirectory()) {
10612
- return `Error: Path is not a directory: ${resolvedPath}`;
10826
+ return `Error: Path is not a directory: ${normalizedPath}`;
10613
10827
  }
10614
10828
  } catch (error) {
10615
- return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
10829
+ return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10616
10830
  }
10617
- const config = loadEditableConfig();
10618
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10619
- const normalizedPath = path9.normalize(resolvedPath);
10620
- const alreadyExists = knowledgeBases.some(
10621
- (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
10622
- );
10831
+ const config = loadEditableConfig(projectRoot);
10832
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10833
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10623
10834
  if (alreadyExists) {
10624
- return `Knowledge base already configured: ${resolvedPath}`;
10835
+ return `Knowledge base already configured: ${normalizedPath}`;
10625
10836
  }
10626
- knowledgeBases.push(resolvedPath);
10837
+ knowledgeBases.push(normalizedPath);
10627
10838
  config.knowledgeBases = knowledgeBases;
10628
- saveConfig(config);
10629
- refreshIndexerFromConfig();
10630
- let result = `${resolvedPath}
10839
+ saveConfig(projectRoot, config);
10840
+ refreshIndexerForDirectory(projectRoot);
10841
+ let result = `${normalizedPath}
10631
10842
  `;
10632
10843
  result += `Total knowledge bases: ${knowledgeBases.length}
10633
10844
  `;
10634
- result += `Config saved to: ${getConfigPath()}
10845
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10635
10846
  `;
10636
10847
  result += `
10637
10848
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10641,9 +10852,10 @@ Run /index to rebuild the index with the new knowledge base.`;
10641
10852
  var list_knowledge_bases = (0, import_plugin.tool)({
10642
10853
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10643
10854
  args: {},
10644
- async execute() {
10645
- const config = loadRuntimeConfig();
10646
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10855
+ async execute(_args, context) {
10856
+ const projectRoot = context?.worktree || defaultProjectRoot;
10857
+ const config = loadRuntimeConfig(projectRoot);
10858
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10647
10859
  if (knowledgeBases.length === 0) {
10648
10860
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
10649
10861
  }
@@ -10652,8 +10864,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10652
10864
  `;
10653
10865
  for (let i = 0; i < knowledgeBases.length; i++) {
10654
10866
  const kb = knowledgeBases[i];
10655
- const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10656
- const exists = (0, import_fs7.existsSync)(resolvedPath);
10867
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10868
+ const exists = (0, import_fs9.existsSync)(resolvedPath);
10657
10869
  result += `[${i + 1}] ${kb}
10658
10870
  `;
10659
10871
  result += ` Resolved: ${resolvedPath}
@@ -10662,7 +10874,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10662
10874
  `;
10663
10875
  if (exists) {
10664
10876
  try {
10665
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10877
+ const stat4 = (0, import_fs9.statSync)(resolvedPath);
10666
10878
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
10667
10879
  `;
10668
10880
  } catch {
@@ -10670,7 +10882,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10670
10882
  }
10671
10883
  result += "\n";
10672
10884
  }
10673
- result += `Config file: ${getConfigPath()}`;
10885
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10674
10886
  return result;
10675
10887
  }
10676
10888
  });
@@ -10679,17 +10891,15 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10679
10891
  args: {
10680
10892
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10681
10893
  },
10682
- async execute(args) {
10894
+ async execute(args, context) {
10895
+ const projectRoot = context?.worktree || defaultProjectRoot;
10683
10896
  const inputPath = args.path.trim();
10684
- const config = loadEditableConfig();
10685
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10897
+ const config = loadEditableConfig(projectRoot);
10898
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10686
10899
  if (knowledgeBases.length === 0) {
10687
10900
  return "No knowledge bases configured.";
10688
10901
  }
10689
- const normalizedInput = path9.normalize(inputPath);
10690
- const index = knowledgeBases.findIndex(
10691
- (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
10692
- );
10902
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10693
10903
  if (index === -1) {
10694
10904
  let result2 = `Knowledge base not found: ${inputPath}
10695
10905
 
@@ -10704,14 +10914,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10704
10914
  }
10705
10915
  const removed = knowledgeBases.splice(index, 1)[0];
10706
10916
  config.knowledgeBases = knowledgeBases;
10707
- saveConfig(config);
10708
- refreshIndexerFromConfig();
10917
+ saveConfig(projectRoot, config);
10918
+ refreshIndexerForDirectory(projectRoot);
10709
10919
  let result = `Removed: ${removed}
10710
10920
 
10711
10921
  `;
10712
10922
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10713
10923
  `;
10714
- result += `Config saved to: ${getConfigPath()}
10924
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10715
10925
  `;
10716
10926
  result += `
10717
10927
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -10720,8 +10930,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
10720
10930
  });
10721
10931
 
10722
10932
  // src/commands/loader.ts
10723
- var import_fs8 = require("fs");
10724
- var path10 = __toESM(require("path"), 1);
10933
+ var import_fs10 = require("fs");
10934
+ var path16 = __toESM(require("path"), 1);
10725
10935
  function parseFrontmatter(content) {
10726
10936
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10727
10937
  const match = content.match(frontmatterRegex);
@@ -10742,15 +10952,21 @@ function parseFrontmatter(content) {
10742
10952
  }
10743
10953
  function loadCommandsFromDirectory(commandsDir) {
10744
10954
  const commands = /* @__PURE__ */ new Map();
10745
- if (!(0, import_fs8.existsSync)(commandsDir)) {
10955
+ if (!(0, import_fs10.existsSync)(commandsDir)) {
10746
10956
  return commands;
10747
10957
  }
10748
- const files = (0, import_fs8.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10958
+ const files = (0, import_fs10.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10749
10959
  for (const file of files) {
10750
- const filePath = path10.join(commandsDir, file);
10751
- const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
10960
+ const filePath = path16.join(commandsDir, file);
10961
+ let content;
10962
+ try {
10963
+ content = (0, import_fs10.readFileSync)(filePath, "utf-8");
10964
+ } catch (error) {
10965
+ const message = error instanceof Error ? error.message : String(error);
10966
+ throw new Error(`Failed to load command file ${filePath}: ${message}`);
10967
+ }
10752
10968
  const { frontmatter, body } = parseFrontmatter(content);
10753
- const name = path10.basename(file, ".md");
10969
+ const name = path16.basename(file, ".md");
10754
10970
  const description = frontmatter.description || `Run the ${name} command`;
10755
10971
  commands.set(name, {
10756
10972
  description,
@@ -10760,7 +10976,7 @@ function loadCommandsFromDirectory(commandsDir) {
10760
10976
  return commands;
10761
10977
  }
10762
10978
 
10763
- // src/routing-hints.ts
10979
+ // src/routing-hints-patterns.ts
10764
10980
  var EXTERNAL_HINTS = [
10765
10981
  "docs",
10766
10982
  "documentation",
@@ -10851,6 +11067,8 @@ var SNAKE_PATTERN = /\b[a-z0-9]+_[a-z0-9_]+\b/g;
10851
11067
  var KEBAB_PATTERN = /\b[a-z0-9]+-[a-z0-9-]+\b/g;
10852
11068
  var BACKTICK_IDENTIFIER_PATTERN = /`([^`]+)`/g;
10853
11069
  var BACKTICK_IDENTIFIER_PRESENCE_PATTERN = /`([^`]+)`/;
11070
+ var DOUBLE_QUOTED_PATTERN = /"[^"]+"/;
11071
+ var SINGLE_QUOTED_PATTERN = /'[^']+'/;
10854
11072
  function normalizeText(text) {
10855
11073
  return text.trim().replace(/\s+/g, " ");
10856
11074
  }
@@ -10863,6 +11081,21 @@ function countWords(text) {
10863
11081
  }
10864
11082
  return text.split(/\s+/).filter(Boolean).length;
10865
11083
  }
11084
+ function isExternalLookup(text) {
11085
+ return URL_PATTERN.test(text) || includesHint(text, EXTERNAL_HINTS);
11086
+ }
11087
+ function hasConceptualDiscoveryHint(text) {
11088
+ return includesHint(text, CONCEPTUAL_DISCOVERY_HINTS);
11089
+ }
11090
+ function hasDefinitionHint(text) {
11091
+ return includesHint(text, DEFINITION_HINTS);
11092
+ }
11093
+ function hasExactMatchHint(text) {
11094
+ return includesHint(text, EXACT_MATCH_HINTS);
11095
+ }
11096
+ function hasNonDiscoveryHint(text) {
11097
+ return includesHint(text, NON_DISCOVERY_HINTS);
11098
+ }
10866
11099
  function hasIdentifierShape(text) {
10867
11100
  const matches = [
10868
11101
  ...text.match(CAMEL_OR_PASCAL_PATTERN) ?? [],
@@ -10878,11 +11111,13 @@ function hasIdentifierShape(text) {
10878
11111
  });
10879
11112
  }
10880
11113
  function containsQuotedIdentifier(text) {
10881
- return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || /"[^"]+"/.test(text) || /'[^']+'/.test(text);
11114
+ return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || DOUBLE_QUOTED_PATTERN.test(text) || SINGLE_QUOTED_PATTERN.test(text);
10882
11115
  }
10883
11116
  function looksLikeDirectPath(text) {
10884
11117
  return FILE_PATH_PATTERN.test(text) || /\b[a-z0-9_-]+\.(ts|tsx|js|jsx|rs|py|go|java|json|md|yaml|yml)\b/i.test(text);
10885
11118
  }
11119
+
11120
+ // src/routing-hints.ts
10886
11121
  function extractUserText(parts) {
10887
11122
  return normalizeText(
10888
11123
  parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join(" ")
@@ -10898,21 +11133,21 @@ function assessRoutingIntent(text) {
10898
11133
  reason: "empty_text"
10899
11134
  };
10900
11135
  }
10901
- if (URL_PATTERN.test(lowered) || includesHint(lowered, EXTERNAL_HINTS)) {
11136
+ if (isExternalLookup(lowered)) {
10902
11137
  return {
10903
11138
  intent: "external",
10904
11139
  text: normalizedText,
10905
11140
  reason: "external_lookup"
10906
11141
  };
10907
11142
  }
10908
- const hasConceptualHint = includesHint(lowered, CONCEPTUAL_DISCOVERY_HINTS);
10909
- const hasDefinitionHint = includesHint(lowered, DEFINITION_HINTS);
10910
- const hasExactMatchHint = includesHint(lowered, EXACT_MATCH_HINTS);
10911
- const hasNonDiscoveryHint = includesHint(lowered, NON_DISCOVERY_HINTS);
11143
+ const matchedConceptualHint = hasConceptualDiscoveryHint(lowered);
11144
+ const matchedDefinitionHint = hasDefinitionHint(lowered);
11145
+ const matchedExactMatchHint = hasExactMatchHint(lowered);
11146
+ const matchedNonDiscoveryHint = hasNonDiscoveryHint(lowered);
10912
11147
  const hasIdentifier = hasIdentifierShape(normalizedText);
10913
11148
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
10914
11149
  const shortQuery = countWords(lowered) <= 10;
10915
- if (hasNonDiscoveryHint && !hasConceptualHint) {
11150
+ if (matchedNonDiscoveryHint && !matchedConceptualHint) {
10916
11151
  return {
10917
11152
  intent: "other",
10918
11153
  text: normalizedText,
@@ -10926,21 +11161,21 @@ function assessRoutingIntent(text) {
10926
11161
  reason: "direct_path_reference"
10927
11162
  };
10928
11163
  }
10929
- if ((hasDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
11164
+ if ((matchedDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
10930
11165
  return {
10931
11166
  intent: "definition_lookup",
10932
11167
  text: normalizedText,
10933
11168
  reason: "definition_lookup_request"
10934
11169
  };
10935
11170
  }
10936
- if ((hasExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !hasConceptualHint && shortQuery) {
11171
+ if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
10937
11172
  return {
10938
11173
  intent: "exact_identifier",
10939
11174
  text: normalizedText,
10940
- reason: hasExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
11175
+ reason: matchedExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
10941
11176
  };
10942
11177
  }
10943
- if (hasConceptualHint) {
11178
+ if (matchedConceptualHint) {
10944
11179
  return {
10945
11180
  intent: "local_conceptual",
10946
11181
  text: normalizedText,
@@ -11030,33 +11265,55 @@ var RoutingHintController = class {
11030
11265
 
11031
11266
  // src/index.ts
11032
11267
  var import_meta2 = {};
11033
- var activeWatcher = null;
11034
- function replaceActiveWatcher(nextWatcher) {
11035
- activeWatcher?.stop();
11036
- activeWatcher = nextWatcher;
11268
+ var activeWatchers = /* @__PURE__ */ new Map();
11269
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11270
+ const existing = activeWatchers.get(projectRoot);
11271
+ if (existing) {
11272
+ existing.stop();
11273
+ activeWatchers.delete(projectRoot);
11274
+ }
11275
+ if (nextWatcher) {
11276
+ activeWatchers.set(projectRoot, nextWatcher);
11277
+ }
11037
11278
  }
11038
11279
  function getCommandsDir() {
11039
11280
  let currentDir = process.cwd();
11040
11281
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
11041
- currentDir = path11.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11282
+ currentDir = path17.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11283
+ }
11284
+ return path17.join(currentDir, "..", "commands");
11285
+ }
11286
+ function appendRoutingHints(output, hints, preferredRole) {
11287
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11288
+ if (Array.isArray(preferredBucket)) {
11289
+ preferredBucket.push(...hints);
11290
+ return;
11291
+ }
11292
+ if (Array.isArray(output.system)) {
11293
+ output.system.push(...hints);
11042
11294
  }
11043
- return path11.join(currentDir, "..", "commands");
11044
11295
  }
11045
- var plugin = async ({ directory }) => {
11296
+ var plugin = async ({ directory, worktree }) => {
11046
11297
  try {
11047
- const projectRoot = directory;
11298
+ const projectRoot = worktree || directory;
11048
11299
  const rawConfig = loadMergedConfig(projectRoot);
11049
11300
  const config = parseConfig(rawConfig);
11050
11301
  initializeTools(projectRoot, config);
11051
- const indexer = getSharedIndexer();
11052
- const routingHints = config.search.routingHints ? new RoutingHintController(() => indexer.getStatus()) : null;
11053
- const isValidProject = !config.indexing.requireProjectMarker || hasProjectMarker(projectRoot);
11054
- if (!isValidProject) {
11302
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11303
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11304
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11305
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11306
+ if (isHomeDir) {
11307
+ console.warn(
11308
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11309
+ );
11310
+ } else if (!isValidProject) {
11055
11311
  console.warn(
11056
11312
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11057
11313
  );
11058
11314
  }
11059
11315
  if (config.indexing.autoIndex && isValidProject) {
11316
+ const indexer = getProjectIndexer();
11060
11317
  indexer.initialize().then(() => {
11061
11318
  indexer.index().catch(() => {
11062
11319
  });
@@ -11064,9 +11321,9 @@ var plugin = async ({ directory }) => {
11064
11321
  });
11065
11322
  }
11066
11323
  if (config.indexing.watchFiles && isValidProject) {
11067
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11324
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11068
11325
  } else {
11069
- replaceActiveWatcher(null);
11326
+ replaceActiveWatcher(projectRoot, null);
11070
11327
  }
11071
11328
  return {
11072
11329
  tool: {
@@ -11088,8 +11345,18 @@ var plugin = async ({ directory }) => {
11088
11345
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11089
11346
  },
11090
11347
  async "experimental.chat.system.transform"(input, output) {
11348
+ if (config.search.routingHintRole !== "system") {
11349
+ return;
11350
+ }
11351
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11352
+ appendRoutingHints(output, hints, "system");
11353
+ },
11354
+ async "experimental.chat.developer.transform"(input, output) {
11355
+ if (config.search.routingHintRole !== "developer") {
11356
+ return;
11357
+ }
11091
11358
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11092
- output.system.push(...hints);
11359
+ appendRoutingHints(output, hints, "developer");
11093
11360
  },
11094
11361
  async "tool.execute.after"(input) {
11095
11362
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11103,8 +11370,8 @@ var plugin = async ({ directory }) => {
11103
11370
  }
11104
11371
  }
11105
11372
  };
11106
- } catch (error) {
11107
- console.error("[codebase-index] Failed to initialize plugin:", error);
11373
+ } catch {
11374
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11108
11375
  return {
11109
11376
  tool: void 0,
11110
11377
  async config() {