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.js CHANGED
@@ -324,7 +324,7 @@ var require_ignore = __commonJS({
324
324
  // path matching.
325
325
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
326
326
  // @returns {TestResult} true if a file is ignored
327
- test(path12, checkUnignored, mode) {
327
+ test(path18, checkUnignored, mode) {
328
328
  let ignored = false;
329
329
  let unignored = false;
330
330
  let matchedRule;
@@ -333,7 +333,7 @@ var require_ignore = __commonJS({
333
333
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
334
334
  return;
335
335
  }
336
- const matched = rule[mode].test(path12);
336
+ const matched = rule[mode].test(path18);
337
337
  if (!matched) {
338
338
  return;
339
339
  }
@@ -354,17 +354,17 @@ var require_ignore = __commonJS({
354
354
  var throwError = (message, Ctor) => {
355
355
  throw new Ctor(message);
356
356
  };
357
- var checkPath = (path12, originalPath, doThrow) => {
358
- if (!isString(path12)) {
357
+ var checkPath = (path18, originalPath, doThrow) => {
358
+ if (!isString(path18)) {
359
359
  return doThrow(
360
360
  `path must be a string, but got \`${originalPath}\``,
361
361
  TypeError
362
362
  );
363
363
  }
364
- if (!path12) {
364
+ if (!path18) {
365
365
  return doThrow(`path must not be empty`, TypeError);
366
366
  }
367
- if (checkPath.isNotRelative(path12)) {
367
+ if (checkPath.isNotRelative(path18)) {
368
368
  const r = "`path.relative()`d";
369
369
  return doThrow(
370
370
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -373,7 +373,7 @@ var require_ignore = __commonJS({
373
373
  }
374
374
  return true;
375
375
  };
376
- var isNotRelative = (path12) => REGEX_TEST_INVALID_PATH.test(path12);
376
+ var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
377
377
  checkPath.isNotRelative = isNotRelative;
378
378
  checkPath.convert = (p) => p;
379
379
  var Ignore2 = class {
@@ -403,19 +403,19 @@ var require_ignore = __commonJS({
403
403
  }
404
404
  // @returns {TestResult}
405
405
  _test(originalPath, cache, checkUnignored, slices) {
406
- const path12 = originalPath && checkPath.convert(originalPath);
406
+ const path18 = originalPath && checkPath.convert(originalPath);
407
407
  checkPath(
408
- path12,
408
+ path18,
409
409
  originalPath,
410
410
  this._strictPathCheck ? throwError : RETURN_FALSE
411
411
  );
412
- return this._t(path12, cache, checkUnignored, slices);
412
+ return this._t(path18, cache, checkUnignored, slices);
413
413
  }
414
- checkIgnore(path12) {
415
- if (!REGEX_TEST_TRAILING_SLASH.test(path12)) {
416
- return this.test(path12);
414
+ checkIgnore(path18) {
415
+ if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
416
+ return this.test(path18);
417
417
  }
418
- const slices = path12.split(SLASH2).filter(Boolean);
418
+ const slices = path18.split(SLASH2).filter(Boolean);
419
419
  slices.pop();
420
420
  if (slices.length) {
421
421
  const parent = this._t(
@@ -428,18 +428,18 @@ var require_ignore = __commonJS({
428
428
  return parent;
429
429
  }
430
430
  }
431
- return this._rules.test(path12, false, MODE_CHECK_IGNORE);
431
+ return this._rules.test(path18, false, MODE_CHECK_IGNORE);
432
432
  }
433
- _t(path12, cache, checkUnignored, slices) {
434
- if (path12 in cache) {
435
- return cache[path12];
433
+ _t(path18, cache, checkUnignored, slices) {
434
+ if (path18 in cache) {
435
+ return cache[path18];
436
436
  }
437
437
  if (!slices) {
438
- slices = path12.split(SLASH2).filter(Boolean);
438
+ slices = path18.split(SLASH2).filter(Boolean);
439
439
  }
440
440
  slices.pop();
441
441
  if (!slices.length) {
442
- return cache[path12] = this._rules.test(path12, checkUnignored, MODE_IGNORE);
442
+ return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
443
443
  }
444
444
  const parent = this._t(
445
445
  slices.join(SLASH2) + SLASH2,
@@ -447,29 +447,29 @@ var require_ignore = __commonJS({
447
447
  checkUnignored,
448
448
  slices
449
449
  );
450
- return cache[path12] = parent.ignored ? parent : this._rules.test(path12, checkUnignored, MODE_IGNORE);
450
+ return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
451
451
  }
452
- ignores(path12) {
453
- return this._test(path12, this._ignoreCache, false).ignored;
452
+ ignores(path18) {
453
+ return this._test(path18, this._ignoreCache, false).ignored;
454
454
  }
455
455
  createFilter() {
456
- return (path12) => !this.ignores(path12);
456
+ return (path18) => !this.ignores(path18);
457
457
  }
458
458
  filter(paths) {
459
459
  return makeArray(paths).filter(this.createFilter());
460
460
  }
461
461
  // @returns {TestResult}
462
- test(path12) {
463
- return this._test(path12, this._testCache, true);
462
+ test(path18) {
463
+ return this._test(path18, this._testCache, true);
464
464
  }
465
465
  };
466
466
  var factory = (options) => new Ignore2(options);
467
- var isPathValid = (path12) => checkPath(path12 && checkPath.convert(path12), path12, RETURN_FALSE);
467
+ var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
468
468
  var setupWindows = () => {
469
469
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
470
470
  checkPath.convert = makePosix;
471
471
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
472
- checkPath.isNotRelative = (path12) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path12) || isNotRelative(path12);
472
+ checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
473
473
  };
474
474
  if (
475
475
  // Detect `process` so that it can run in browsers.
@@ -647,7 +647,8 @@ var require_eventemitter3 = __commonJS({
647
647
  });
648
648
 
649
649
  // src/index.ts
650
- import * as path11 from "path";
650
+ import * as os6 from "os";
651
+ import * as path17 from "path";
651
652
  import { fileURLToPath as fileURLToPath2 } from "url";
652
653
 
653
654
  // src/config/constants.ts
@@ -664,7 +665,8 @@ var DEFAULT_INCLUDE = [
664
665
  "**/*.{md,mdx}",
665
666
  "**/*.{sh,bash,zsh}",
666
667
  "**/*.{txt,html,htm}",
667
- "**/*.zig"
668
+ "**/*.zig",
669
+ "**/*.gd"
668
670
  ];
669
671
  var DEFAULT_EXCLUDE = [
670
672
  "**/node_modules/**",
@@ -763,28 +765,7 @@ var AUTO_DETECT_PROVIDER_ORDER = [
763
765
  "google"
764
766
  ];
765
767
 
766
- // src/config/env-substitution.ts
767
- var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
768
- var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
769
- function substituteEnvString(value, keyPath) {
770
- const match = value.match(ENV_REFERENCE_PATTERN);
771
- if (!match) {
772
- if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
773
- throw new Error(
774
- `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
775
- );
776
- }
777
- return value;
778
- }
779
- const variableName = match[1];
780
- const envValue = process.env[variableName];
781
- if (envValue === void 0) {
782
- throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
783
- }
784
- return envValue;
785
- }
786
-
787
- // src/config/schema.ts
768
+ // src/config/defaults.ts
788
769
  function getDefaultIndexingConfig() {
789
770
  return {
790
771
  autoIndex: false,
@@ -813,15 +794,10 @@ function getDefaultSearchConfig() {
813
794
  rrfK: 60,
814
795
  rerankTopN: 20,
815
796
  contextLines: 0,
816
- routingHints: true
797
+ routingHints: true,
798
+ routingHintRole: "system"
817
799
  };
818
800
  }
819
- function isValidFusionStrategy(value) {
820
- return value === "weighted" || value === "rrf";
821
- }
822
- function isValidRerankerProvider(value) {
823
- return value === "cohere" || value === "jina" || value === "custom";
824
- }
825
801
  function getDefaultRerankerBaseUrl(provider) {
826
802
  switch (provider) {
827
803
  case "cohere":
@@ -844,8 +820,37 @@ function getDefaultDebugConfig() {
844
820
  metrics: true
845
821
  };
846
822
  }
823
+
824
+ // src/config/env-substitution.ts
825
+ var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
826
+ var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
827
+ function substituteEnvString(value, keyPath) {
828
+ const match = value.match(ENV_REFERENCE_PATTERN);
829
+ if (!match) {
830
+ if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
831
+ throw new Error(
832
+ `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
833
+ );
834
+ }
835
+ return value;
836
+ }
837
+ const variableName = match[1];
838
+ const envValue = process.env[variableName];
839
+ if (envValue === void 0) {
840
+ throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
841
+ }
842
+ return envValue;
843
+ }
844
+
845
+ // src/config/validators.ts
847
846
  var VALID_SCOPES = ["project", "global"];
848
847
  var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
848
+ function isValidFusionStrategy(value) {
849
+ return value === "weighted" || value === "rrf";
850
+ }
851
+ function isValidRerankerProvider(value) {
852
+ return value === "cohere" || value === "jina" || value === "custom";
853
+ }
849
854
  function isValidProvider(value) {
850
855
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
851
856
  }
@@ -873,6 +878,8 @@ function getResolvedStringArray(value, keyPath) {
873
878
  function isValidLogLevel(value) {
874
879
  return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
875
880
  }
881
+
882
+ // src/config/schema.ts
876
883
  function parseConfig(raw) {
877
884
  const input = raw && typeof raw === "object" ? raw : {};
878
885
  const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
@@ -909,7 +916,8 @@ function parseConfig(raw) {
909
916
  rrfK: typeof rawSearch.rrfK === "number" ? Math.max(1, Math.floor(rawSearch.rrfK)) : defaultSearch.rrfK,
910
917
  rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
911
918
  contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
912
- routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints
919
+ routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
920
+ routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
913
921
  };
914
922
  const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
915
923
  const debug = {
@@ -927,9 +935,9 @@ function parseConfig(raw) {
927
935
  const rawAdditionalInclude = input.additionalInclude;
928
936
  const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
929
937
  let embeddingProvider;
930
- let embeddingModel = void 0;
931
- let customProvider = void 0;
932
- let reranker = void 0;
938
+ let embeddingModel;
939
+ let customProvider;
940
+ let reranker;
933
941
  if (embeddingProviderValue === "custom") {
934
942
  embeddingProvider = "custom";
935
943
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -962,7 +970,7 @@ function parseConfig(raw) {
962
970
  embeddingProvider = embeddingProviderValue;
963
971
  const rawEmbeddingModel = input.embeddingModel;
964
972
  if (typeof rawEmbeddingModel === "string") {
965
- const embeddingModelValue = substituteEnvString(rawEmbeddingModel, "$root.embeddingModel");
973
+ const embeddingModelValue = getResolvedString(rawEmbeddingModel, "$root.embeddingModel");
966
974
  if (embeddingModelValue) {
967
975
  embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
968
976
  }
@@ -1025,16 +1033,20 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1025
1033
  );
1026
1034
 
1027
1035
  // src/config/merger.ts
1028
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1036
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
1029
1037
  import * as os2 from "os";
1030
- import * as path3 from "path";
1038
+ import * as path6 from "path";
1031
1039
 
1032
1040
  // src/config/paths.ts
1033
- import { existsSync as existsSync2 } from "fs";
1041
+ import { existsSync as existsSync3 } from "fs";
1034
1042
  import * as os from "os";
1035
- import * as path2 from "path";
1043
+ import * as path3 from "path";
1036
1044
 
1037
1045
  // src/git/index.ts
1046
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1047
+ import * as path2 from "path";
1048
+
1049
+ // src/git/refs.ts
1038
1050
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
1039
1051
  import * as path from "path";
1040
1052
  function readPackedRefs(gitDir) {
@@ -1067,38 +1079,40 @@ function resolveCommonGitDir(gitDir) {
1067
1079
  }
1068
1080
  return gitDir;
1069
1081
  }
1082
+
1083
+ // src/git/index.ts
1070
1084
  function resolveWorktreeMainRepoRoot(repoRoot) {
1071
1085
  const gitDir = resolveGitDir(repoRoot);
1072
1086
  if (!gitDir) {
1073
1087
  return null;
1074
1088
  }
1075
1089
  const commonGitDir = resolveCommonGitDir(gitDir);
1076
- if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1090
+ if (commonGitDir === gitDir || path2.basename(commonGitDir) !== ".git") {
1077
1091
  return null;
1078
1092
  }
1079
- const mainRepoRoot = path.dirname(commonGitDir);
1080
- if (!existsSync(mainRepoRoot)) {
1093
+ const mainRepoRoot = path2.dirname(commonGitDir);
1094
+ if (!existsSync2(mainRepoRoot)) {
1081
1095
  return null;
1082
1096
  }
1083
- return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1097
+ return path2.resolve(mainRepoRoot) === path2.resolve(repoRoot) ? null : mainRepoRoot;
1084
1098
  }
1085
1099
  function resolveGitDir(repoRoot) {
1086
- const gitPath = path.join(repoRoot, ".git");
1087
- if (!existsSync(gitPath)) {
1100
+ const gitPath = path2.join(repoRoot, ".git");
1101
+ if (!existsSync2(gitPath)) {
1088
1102
  return null;
1089
1103
  }
1090
1104
  try {
1091
- const stat4 = statSync(gitPath);
1105
+ const stat4 = statSync2(gitPath);
1092
1106
  if (stat4.isDirectory()) {
1093
1107
  return gitPath;
1094
1108
  }
1095
1109
  if (stat4.isFile()) {
1096
- const content = readFileSync(gitPath, "utf-8").trim();
1110
+ const content = readFileSync2(gitPath, "utf-8").trim();
1097
1111
  const match = content.match(/^gitdir:\s*(.+)$/);
1098
1112
  if (match) {
1099
1113
  const gitdir = match[1];
1100
- const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1101
- if (existsSync(resolvedPath)) {
1114
+ const resolvedPath = path2.isAbsolute(gitdir) ? gitdir : path2.resolve(repoRoot, gitdir);
1115
+ if (existsSync2(resolvedPath)) {
1102
1116
  return resolvedPath;
1103
1117
  }
1104
1118
  }
@@ -1115,12 +1129,12 @@ function getCurrentBranch(repoRoot) {
1115
1129
  if (!gitDir) {
1116
1130
  return null;
1117
1131
  }
1118
- const headPath = path.join(gitDir, "HEAD");
1119
- if (!existsSync(headPath)) {
1132
+ const headPath = path2.join(gitDir, "HEAD");
1133
+ if (!existsSync2(headPath)) {
1120
1134
  return null;
1121
1135
  }
1122
1136
  try {
1123
- const headContent = readFileSync(headPath, "utf-8").trim();
1137
+ const headContent = readFileSync2(headPath, "utf-8").trim();
1124
1138
  const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1125
1139
  if (match) {
1126
1140
  return match[1];
@@ -1139,8 +1153,8 @@ function getBaseBranch(repoRoot) {
1139
1153
  const candidates = ["main", "master", "develop", "trunk"];
1140
1154
  if (refStoreDir) {
1141
1155
  for (const candidate of candidates) {
1142
- const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1143
- if (existsSync(refPath)) {
1156
+ const refPath = path2.join(refStoreDir, "refs", "heads", candidate);
1157
+ if (existsSync2(refPath)) {
1144
1158
  return candidate;
1145
1159
  }
1146
1160
  const packedRefs = readPackedRefs(refStoreDir);
@@ -1160,44 +1174,44 @@ function getBranchOrDefault(repoRoot) {
1160
1174
  function getHeadPath(repoRoot) {
1161
1175
  const gitDir = resolveGitDir(repoRoot);
1162
1176
  if (gitDir) {
1163
- return path.join(gitDir, "HEAD");
1177
+ return path2.join(gitDir, "HEAD");
1164
1178
  }
1165
- return path.join(repoRoot, ".git", "HEAD");
1179
+ return path2.join(repoRoot, ".git", "HEAD");
1166
1180
  }
1167
1181
 
1168
1182
  // src/config/paths.ts
1169
- var PROJECT_CONFIG_RELATIVE_PATH = path2.join(".opencode", "codebase-index.json");
1170
- var PROJECT_INDEX_RELATIVE_PATH = path2.join(".opencode", "index");
1183
+ var PROJECT_CONFIG_RELATIVE_PATH = path3.join(".opencode", "codebase-index.json");
1184
+ var PROJECT_INDEX_RELATIVE_PATH = path3.join(".opencode", "index");
1171
1185
  function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1172
1186
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1173
1187
  if (!mainRepoRoot) {
1174
1188
  return null;
1175
1189
  }
1176
- const fallbackPath = path2.join(mainRepoRoot, relativePath);
1177
- return existsSync2(fallbackPath) ? fallbackPath : null;
1190
+ const fallbackPath = path3.join(mainRepoRoot, relativePath);
1191
+ return existsSync3(fallbackPath) ? fallbackPath : null;
1178
1192
  }
1179
1193
  function hasProjectConfig(projectRoot) {
1180
- return existsSync2(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1194
+ return existsSync3(path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1181
1195
  }
1182
1196
  function getGlobalIndexPath() {
1183
- return path2.join(os.homedir(), ".opencode", "global-index");
1197
+ return path3.join(os.homedir(), ".opencode", "global-index");
1184
1198
  }
1185
1199
  function resolveProjectConfigPath(projectRoot) {
1186
- const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1187
- if (existsSync2(localConfigPath)) {
1200
+ const localConfigPath = path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1201
+ if (existsSync3(localConfigPath)) {
1188
1202
  return localConfigPath;
1189
1203
  }
1190
1204
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1191
1205
  }
1192
1206
  function resolveWritableProjectConfigPath(projectRoot) {
1193
- return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1207
+ return path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1194
1208
  }
1195
1209
  function resolveProjectIndexPath(projectRoot, scope) {
1196
1210
  if (scope === "global") {
1197
1211
  return getGlobalIndexPath();
1198
1212
  }
1199
- const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1200
- if (existsSync2(localIndexPath)) {
1213
+ const localIndexPath = path3.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1214
+ if (existsSync3(localIndexPath)) {
1201
1215
  return localIndexPath;
1202
1216
  }
1203
1217
  if (hasProjectConfig(projectRoot)) {
@@ -1206,23 +1220,56 @@ function resolveProjectIndexPath(projectRoot, scope) {
1206
1220
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1207
1221
  }
1208
1222
 
1209
- // src/config/merger.ts
1210
- function loadJsonFile(filePath) {
1211
- try {
1212
- if (existsSync3(filePath)) {
1213
- const content = readFileSync2(filePath, "utf-8");
1214
- return JSON.parse(content);
1215
- }
1216
- } catch {
1217
- }
1218
- return null;
1223
+ // src/config/rebase.ts
1224
+ import * as path5 from "path";
1225
+
1226
+ // src/utils/paths.ts
1227
+ import * as path4 from "path";
1228
+ function normalizePathSeparators(value) {
1229
+ return value.replace(/\\/g, "/");
1219
1230
  }
1220
- function normalizeRelativeConfigPath(candidate) {
1221
- return candidate.replace(/\\/g, "/");
1231
+ function isHiddenPathSegment(part) {
1232
+ return part.startsWith(".") && part !== "." && part !== "..";
1222
1233
  }
1234
+ function isBuildPathSegment(part) {
1235
+ return part.toLowerCase().includes("build");
1236
+ }
1237
+ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1238
+ return relativePath.split(separator).some(
1239
+ (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1240
+ );
1241
+ }
1242
+ var RESTRICTED_DIRECTORIES = /* @__PURE__ */ new Set([
1243
+ // macOS
1244
+ "library",
1245
+ "applications",
1246
+ "system",
1247
+ "volumes",
1248
+ "private",
1249
+ "cores",
1250
+ // Linux
1251
+ "proc",
1252
+ "sys",
1253
+ "dev",
1254
+ "run",
1255
+ "snap",
1256
+ // Windows
1257
+ "windows",
1258
+ "programdata",
1259
+ "program files",
1260
+ "program files (x86)",
1261
+ "$recycle.bin"
1262
+ ]);
1263
+ function isRestrictedDirectory(relativePath, separator = path4.sep) {
1264
+ const firstSegment = relativePath.split(separator)[0];
1265
+ if (!firstSegment) return false;
1266
+ return RESTRICTED_DIRECTORIES.has(firstSegment.toLowerCase());
1267
+ }
1268
+
1269
+ // src/config/rebase.ts
1223
1270
  function isWithinRoot(rootDir, targetPath) {
1224
- const relativePath = path3.relative(rootDir, targetPath);
1225
- return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1271
+ const relativePath = path5.relative(rootDir, targetPath);
1272
+ return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
1226
1273
  }
1227
1274
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1228
1275
  if (!Array.isArray(values)) {
@@ -1233,22 +1280,106 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1233
1280
  if (!trimmed) {
1234
1281
  return trimmed;
1235
1282
  }
1236
- if (path3.isAbsolute(trimmed)) {
1283
+ if (path5.isAbsolute(trimmed)) {
1237
1284
  if (isWithinRoot(sourceRoot, trimmed)) {
1238
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1285
+ return normalizePathSeparators(path5.normalize(path5.relative(sourceRoot, trimmed) || "."));
1239
1286
  }
1240
- return path3.normalize(trimmed);
1287
+ return path5.normalize(trimmed);
1241
1288
  }
1242
- const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1289
+ const resolvedFromSource = path5.resolve(sourceRoot, trimmed);
1243
1290
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1244
- return normalizeRelativeConfigPath(path3.normalize(trimmed));
1291
+ return normalizePathSeparators(path5.normalize(trimmed));
1245
1292
  }
1246
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1293
+ return normalizePathSeparators(path5.normalize(path5.relative(targetRoot, resolvedFromSource)));
1247
1294
  }).filter(Boolean);
1248
1295
  }
1296
+
1297
+ // src/config/merger.ts
1298
+ var PROJECT_OVERRIDE_KEYS = [
1299
+ "embeddingProvider",
1300
+ "customProvider",
1301
+ "embeddingModel",
1302
+ "reranker",
1303
+ "include",
1304
+ "exclude",
1305
+ "indexing",
1306
+ "search",
1307
+ "debug",
1308
+ "scope"
1309
+ ];
1310
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1311
+ function isRecord(value) {
1312
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1313
+ }
1314
+ function isStringArray2(value) {
1315
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1316
+ }
1317
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1318
+ if (key in normalizedProjectConfig) {
1319
+ merged[key] = normalizedProjectConfig[key];
1320
+ return;
1321
+ }
1322
+ if (key in globalConfig) {
1323
+ merged[key] = globalConfig[key];
1324
+ }
1325
+ }
1326
+ function mergeUniqueStringArray(values) {
1327
+ return [...new Set(values.map((value) => String(value).trim()))];
1328
+ }
1329
+ function normalizeKnowledgeBasePath(value) {
1330
+ let normalized = path6.normalize(String(value).trim());
1331
+ const root = path6.parse(normalized).root;
1332
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1333
+ normalized = normalized.slice(0, -1);
1334
+ }
1335
+ return normalized;
1336
+ }
1337
+ function mergeKnowledgeBasePaths(values) {
1338
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1339
+ }
1340
+ function validateConfigLayerShape(rawConfig, filePath) {
1341
+ if (!isRecord(rawConfig)) {
1342
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1343
+ }
1344
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1345
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1346
+ }
1347
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1348
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1349
+ }
1350
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1351
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1352
+ }
1353
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1354
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1355
+ }
1356
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1357
+ const value = rawConfig[section];
1358
+ if (value !== void 0 && !isRecord(value)) {
1359
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1360
+ }
1361
+ }
1362
+ return rawConfig;
1363
+ }
1364
+ function loadJsonFile(filePath) {
1365
+ if (!existsSync4(filePath)) {
1366
+ return null;
1367
+ }
1368
+ try {
1369
+ const content = readFileSync3(filePath, "utf-8");
1370
+ return validateConfigLayerShape(JSON.parse(content), filePath);
1371
+ } catch (error) {
1372
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
1373
+ throw error;
1374
+ }
1375
+ const message = error instanceof Error ? error.message : String(error);
1376
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
1377
+ }
1378
+ return null;
1379
+ }
1249
1380
  function materializeLocalProjectConfig(projectRoot, config) {
1250
- const localConfigPath = path3.join(projectRoot, ".opencode", "codebase-index.json");
1251
- mkdirSync(path3.dirname(localConfigPath), { recursive: true });
1381
+ const localConfigPath = path6.join(projectRoot, ".opencode", "codebase-index.json");
1382
+ mkdirSync(path6.dirname(localConfigPath), { recursive: true });
1252
1383
  writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1253
1384
  return localConfigPath;
1254
1385
  }
@@ -1259,7 +1390,7 @@ function loadProjectConfigLayer(projectRoot) {
1259
1390
  return {};
1260
1391
  }
1261
1392
  const normalizedConfig = { ...projectConfig };
1262
- const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1393
+ const projectConfigBaseDir = path6.dirname(path6.dirname(projectConfigPath));
1263
1394
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
1264
1395
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1265
1396
  normalizedConfig.knowledgeBases,
@@ -1271,10 +1402,22 @@ function loadProjectConfigLayer(projectRoot) {
1271
1402
  }
1272
1403
  function loadMergedConfig(projectRoot) {
1273
1404
  const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1274
- const globalConfig = loadJsonFile(globalConfigPath);
1275
1405
  const projectConfigPath = resolveProjectConfigPath(projectRoot);
1406
+ let globalConfig = null;
1407
+ let globalConfigError = null;
1408
+ try {
1409
+ globalConfig = loadJsonFile(globalConfigPath);
1410
+ } catch (error) {
1411
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
1412
+ }
1276
1413
  const projectConfig = loadJsonFile(projectConfigPath);
1277
1414
  const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1415
+ if (globalConfigError) {
1416
+ if (!projectConfig) {
1417
+ throw globalConfigError;
1418
+ }
1419
+ globalConfig = null;
1420
+ }
1278
1421
  if (!globalConfig && !projectConfig) {
1279
1422
  return {};
1280
1423
  }
@@ -1284,60 +1427,16 @@ function loadMergedConfig(projectRoot) {
1284
1427
  if (!globalConfig && projectConfig) {
1285
1428
  return normalizedProjectConfig;
1286
1429
  }
1430
+ if (!globalConfig || !projectConfig) {
1431
+ return globalConfig ?? normalizedProjectConfig;
1432
+ }
1287
1433
  const merged = { ...globalConfig };
1288
- if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
1289
- merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
1290
- } else if (globalConfig && globalConfig.embeddingProvider) {
1291
- merged.embeddingProvider = globalConfig.embeddingProvider;
1292
- }
1293
- if (projectConfig && "customProvider" in normalizedProjectConfig) {
1294
- merged.customProvider = normalizedProjectConfig.customProvider;
1295
- } else if (globalConfig && globalConfig.customProvider) {
1296
- merged.customProvider = globalConfig.customProvider;
1297
- }
1298
- if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
1299
- merged.embeddingModel = normalizedProjectConfig.embeddingModel;
1300
- } else if (globalConfig && globalConfig.embeddingModel) {
1301
- merged.embeddingModel = globalConfig.embeddingModel;
1302
- }
1303
- if (projectConfig && "reranker" in normalizedProjectConfig) {
1304
- merged.reranker = normalizedProjectConfig.reranker;
1305
- } else if (globalConfig && globalConfig.reranker) {
1306
- merged.reranker = globalConfig.reranker;
1307
- }
1308
- if (projectConfig && "include" in normalizedProjectConfig) {
1309
- merged.include = normalizedProjectConfig.include;
1310
- } else if (globalConfig && globalConfig.include) {
1311
- merged.include = globalConfig.include;
1312
- }
1313
- if (projectConfig && "exclude" in normalizedProjectConfig) {
1314
- merged.exclude = normalizedProjectConfig.exclude;
1315
- } else if (globalConfig && globalConfig.exclude) {
1316
- merged.exclude = globalConfig.exclude;
1317
- }
1318
- if (projectConfig && "indexing" in normalizedProjectConfig) {
1319
- merged.indexing = normalizedProjectConfig.indexing;
1320
- } else if (globalConfig && globalConfig.indexing) {
1321
- merged.indexing = globalConfig.indexing;
1322
- }
1323
- if (projectConfig && "search" in normalizedProjectConfig) {
1324
- merged.search = normalizedProjectConfig.search;
1325
- } else if (globalConfig && globalConfig.search) {
1326
- merged.search = globalConfig.search;
1327
- }
1328
- if (projectConfig && "debug" in normalizedProjectConfig) {
1329
- merged.debug = normalizedProjectConfig.debug;
1330
- } else if (globalConfig && globalConfig.debug) {
1331
- merged.debug = globalConfig.debug;
1332
- }
1333
- if (projectConfig && "scope" in normalizedProjectConfig) {
1334
- merged.scope = normalizedProjectConfig.scope;
1335
- } else if (globalConfig && "scope" in globalConfig) {
1336
- merged.scope = globalConfig.scope;
1434
+ for (const key of PROJECT_OVERRIDE_KEYS) {
1435
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1337
1436
  }
1338
1437
  if (projectConfig) {
1339
1438
  for (const key of Object.keys(projectConfig)) {
1340
- 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") {
1439
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1341
1440
  continue;
1342
1441
  }
1343
1442
  merged[key] = normalizedProjectConfig[key];
@@ -1346,13 +1445,11 @@ function loadMergedConfig(projectRoot) {
1346
1445
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1347
1446
  const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1348
1447
  const allKbs = [...globalKbs, ...projectKbs];
1349
- const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1350
- merged.knowledgeBases = uniqueKbs;
1448
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1351
1449
  const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1352
1450
  const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1353
1451
  const allAdditional = [...globalAdditional, ...projectAdditional];
1354
- const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
1355
- merged.additionalInclude = uniqueAdditional;
1452
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1356
1453
  return merged;
1357
1454
  }
1358
1455
 
@@ -1446,7 +1543,7 @@ var ReaddirpStream = class extends Readable {
1446
1543
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1447
1544
  const statMethod = opts.lstat ? lstat : stat;
1448
1545
  if (wantBigintFsStats) {
1449
- this._stat = (path12) => statMethod(path12, { bigint: true });
1546
+ this._stat = (path18) => statMethod(path18, { bigint: true });
1450
1547
  } else {
1451
1548
  this._stat = statMethod;
1452
1549
  }
@@ -1471,8 +1568,8 @@ var ReaddirpStream = class extends Readable {
1471
1568
  const par = this.parent;
1472
1569
  const fil = par && par.files;
1473
1570
  if (fil && fil.length > 0) {
1474
- const { path: path12, depth } = par;
1475
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1571
+ const { path: path18, depth } = par;
1572
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1476
1573
  const awaited = await Promise.all(slice);
1477
1574
  for (const entry of awaited) {
1478
1575
  if (!entry)
@@ -1512,20 +1609,20 @@ var ReaddirpStream = class extends Readable {
1512
1609
  this.reading = false;
1513
1610
  }
1514
1611
  }
1515
- async _exploreDir(path12, depth) {
1612
+ async _exploreDir(path18, depth) {
1516
1613
  let files;
1517
1614
  try {
1518
- files = await readdir(path12, this._rdOptions);
1615
+ files = await readdir(path18, this._rdOptions);
1519
1616
  } catch (error) {
1520
1617
  this._onError(error);
1521
1618
  }
1522
- return { files, depth, path: path12 };
1619
+ return { files, depth, path: path18 };
1523
1620
  }
1524
- async _formatEntry(dirent, path12) {
1621
+ async _formatEntry(dirent, path18) {
1525
1622
  let entry;
1526
1623
  const basename5 = this._isDirent ? dirent.name : dirent;
1527
1624
  try {
1528
- const fullPath = presolve(pjoin(path12, basename5));
1625
+ const fullPath = presolve(pjoin(path18, basename5));
1529
1626
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
1530
1627
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1531
1628
  } catch (err) {
@@ -1925,16 +2022,16 @@ var delFromSet = (main, prop, item) => {
1925
2022
  };
1926
2023
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1927
2024
  var FsWatchInstances = /* @__PURE__ */ new Map();
1928
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
2025
+ function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
1929
2026
  const handleEvent = (rawEvent, evPath) => {
1930
- listener(path12);
1931
- emitRaw(rawEvent, evPath, { watchedPath: path12 });
1932
- if (evPath && path12 !== evPath) {
1933
- fsWatchBroadcast(sp.resolve(path12, evPath), KEY_LISTENERS, sp.join(path12, evPath));
2027
+ listener(path18);
2028
+ emitRaw(rawEvent, evPath, { watchedPath: path18 });
2029
+ if (evPath && path18 !== evPath) {
2030
+ fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
1934
2031
  }
1935
2032
  };
1936
2033
  try {
1937
- return fs_watch(path12, {
2034
+ return fs_watch(path18, {
1938
2035
  persistent: options.persistent
1939
2036
  }, handleEvent);
1940
2037
  } catch (error) {
@@ -1950,12 +2047,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1950
2047
  listener(val1, val2, val3);
1951
2048
  });
1952
2049
  };
1953
- var setFsWatchListener = (path12, fullPath, options, handlers) => {
2050
+ var setFsWatchListener = (path18, fullPath, options, handlers) => {
1954
2051
  const { listener, errHandler, rawEmitter } = handlers;
1955
2052
  let cont = FsWatchInstances.get(fullPath);
1956
2053
  let watcher;
1957
2054
  if (!options.persistent) {
1958
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
2055
+ watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
1959
2056
  if (!watcher)
1960
2057
  return;
1961
2058
  return watcher.close.bind(watcher);
@@ -1966,7 +2063,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1966
2063
  addAndConvert(cont, KEY_RAW, rawEmitter);
1967
2064
  } else {
1968
2065
  watcher = createFsWatchInstance(
1969
- path12,
2066
+ path18,
1970
2067
  options,
1971
2068
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1972
2069
  errHandler,
@@ -1981,7 +2078,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1981
2078
  cont.watcherUnusable = true;
1982
2079
  if (isWindows && error.code === "EPERM") {
1983
2080
  try {
1984
- const fd = await open(path12, "r");
2081
+ const fd = await open(path18, "r");
1985
2082
  await fd.close();
1986
2083
  broadcastErr(error);
1987
2084
  } catch (err) {
@@ -2012,7 +2109,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
2012
2109
  };
2013
2110
  };
2014
2111
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2015
- var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2112
+ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2016
2113
  const { listener, rawEmitter } = handlers;
2017
2114
  let cont = FsWatchFileInstances.get(fullPath);
2018
2115
  const copts = cont && cont.options;
@@ -2034,7 +2131,7 @@ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2034
2131
  });
2035
2132
  const currmtime = curr.mtimeMs;
2036
2133
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2037
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
2134
+ foreach(cont.listeners, (listener2) => listener2(path18, curr));
2038
2135
  }
2039
2136
  })
2040
2137
  };
@@ -2064,13 +2161,13 @@ var NodeFsHandler = class {
2064
2161
  * @param listener on fs change
2065
2162
  * @returns closer for the watcher instance
2066
2163
  */
2067
- _watchWithNodeFs(path12, listener) {
2164
+ _watchWithNodeFs(path18, listener) {
2068
2165
  const opts = this.fsw.options;
2069
- const directory = sp.dirname(path12);
2070
- const basename5 = sp.basename(path12);
2166
+ const directory = sp.dirname(path18);
2167
+ const basename5 = sp.basename(path18);
2071
2168
  const parent = this.fsw._getWatchedDir(directory);
2072
2169
  parent.add(basename5);
2073
- const absolutePath = sp.resolve(path12);
2170
+ const absolutePath = sp.resolve(path18);
2074
2171
  const options = {
2075
2172
  persistent: opts.persistent
2076
2173
  };
@@ -2080,12 +2177,12 @@ var NodeFsHandler = class {
2080
2177
  if (opts.usePolling) {
2081
2178
  const enableBin = opts.interval !== opts.binaryInterval;
2082
2179
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2083
- closer = setFsWatchFileListener(path12, absolutePath, options, {
2180
+ closer = setFsWatchFileListener(path18, absolutePath, options, {
2084
2181
  listener,
2085
2182
  rawEmitter: this.fsw._emitRaw
2086
2183
  });
2087
2184
  } else {
2088
- closer = setFsWatchListener(path12, absolutePath, options, {
2185
+ closer = setFsWatchListener(path18, absolutePath, options, {
2089
2186
  listener,
2090
2187
  errHandler: this._boundHandleError,
2091
2188
  rawEmitter: this.fsw._emitRaw
@@ -2107,7 +2204,7 @@ var NodeFsHandler = class {
2107
2204
  let prevStats = stats;
2108
2205
  if (parent.has(basename5))
2109
2206
  return;
2110
- const listener = async (path12, newStats) => {
2207
+ const listener = async (path18, newStats) => {
2111
2208
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2112
2209
  return;
2113
2210
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2121,11 +2218,11 @@ var NodeFsHandler = class {
2121
2218
  this.fsw._emit(EV.CHANGE, file, newStats2);
2122
2219
  }
2123
2220
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2124
- this.fsw._closeFile(path12);
2221
+ this.fsw._closeFile(path18);
2125
2222
  prevStats = newStats2;
2126
2223
  const closer2 = this._watchWithNodeFs(file, listener);
2127
2224
  if (closer2)
2128
- this.fsw._addPathCloser(path12, closer2);
2225
+ this.fsw._addPathCloser(path18, closer2);
2129
2226
  } else {
2130
2227
  prevStats = newStats2;
2131
2228
  }
@@ -2157,7 +2254,7 @@ var NodeFsHandler = class {
2157
2254
  * @param item basename of this item
2158
2255
  * @returns true if no more processing is needed for this entry.
2159
2256
  */
2160
- async _handleSymlink(entry, directory, path12, item) {
2257
+ async _handleSymlink(entry, directory, path18, item) {
2161
2258
  if (this.fsw.closed) {
2162
2259
  return;
2163
2260
  }
@@ -2167,7 +2264,7 @@ var NodeFsHandler = class {
2167
2264
  this.fsw._incrReadyCount();
2168
2265
  let linkPath;
2169
2266
  try {
2170
- linkPath = await fsrealpath(path12);
2267
+ linkPath = await fsrealpath(path18);
2171
2268
  } catch (e) {
2172
2269
  this.fsw._emitReady();
2173
2270
  return true;
@@ -2177,12 +2274,12 @@ var NodeFsHandler = class {
2177
2274
  if (dir.has(item)) {
2178
2275
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2179
2276
  this.fsw._symlinkPaths.set(full, linkPath);
2180
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
2277
+ this.fsw._emit(EV.CHANGE, path18, entry.stats);
2181
2278
  }
2182
2279
  } else {
2183
2280
  dir.add(item);
2184
2281
  this.fsw._symlinkPaths.set(full, linkPath);
2185
- this.fsw._emit(EV.ADD, path12, entry.stats);
2282
+ this.fsw._emit(EV.ADD, path18, entry.stats);
2186
2283
  }
2187
2284
  this.fsw._emitReady();
2188
2285
  return true;
@@ -2212,9 +2309,9 @@ var NodeFsHandler = class {
2212
2309
  return;
2213
2310
  }
2214
2311
  const item = entry.path;
2215
- let path12 = sp.join(directory, item);
2312
+ let path18 = sp.join(directory, item);
2216
2313
  current.add(item);
2217
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
2314
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2218
2315
  return;
2219
2316
  }
2220
2317
  if (this.fsw.closed) {
@@ -2223,11 +2320,11 @@ var NodeFsHandler = class {
2223
2320
  }
2224
2321
  if (item === target || !target && !previous.has(item)) {
2225
2322
  this.fsw._incrReadyCount();
2226
- path12 = sp.join(dir, sp.relative(dir, path12));
2227
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
2323
+ path18 = sp.join(dir, sp.relative(dir, path18));
2324
+ this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2228
2325
  }
2229
2326
  }).on(EV.ERROR, this._boundHandleError);
2230
- return new Promise((resolve9, reject) => {
2327
+ return new Promise((resolve12, reject) => {
2231
2328
  if (!stream)
2232
2329
  return reject();
2233
2330
  stream.once(STR_END, () => {
@@ -2236,7 +2333,7 @@ var NodeFsHandler = class {
2236
2333
  return;
2237
2334
  }
2238
2335
  const wasThrottled = throttler ? throttler.clear() : false;
2239
- resolve9(void 0);
2336
+ resolve12(void 0);
2240
2337
  previous.getChildren().filter((item) => {
2241
2338
  return item !== directory && !current.has(item);
2242
2339
  }).forEach((item) => {
@@ -2293,13 +2390,13 @@ var NodeFsHandler = class {
2293
2390
  * @param depth Child path actually targeted for watch
2294
2391
  * @param target Child path actually targeted for watch
2295
2392
  */
2296
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2393
+ async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2297
2394
  const ready = this.fsw._emitReady;
2298
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2395
+ if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2299
2396
  ready();
2300
2397
  return false;
2301
2398
  }
2302
- const wh = this.fsw._getWatchHelpers(path12);
2399
+ const wh = this.fsw._getWatchHelpers(path18);
2303
2400
  if (priorWh) {
2304
2401
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2305
2402
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2315,8 +2412,8 @@ var NodeFsHandler = class {
2315
2412
  const follow = this.fsw.options.followSymlinks;
2316
2413
  let closer;
2317
2414
  if (stats.isDirectory()) {
2318
- const absPath = sp.resolve(path12);
2319
- const targetPath = follow ? await fsrealpath(path12) : path12;
2415
+ const absPath = sp.resolve(path18);
2416
+ const targetPath = follow ? await fsrealpath(path18) : path18;
2320
2417
  if (this.fsw.closed)
2321
2418
  return;
2322
2419
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2326,29 +2423,29 @@ var NodeFsHandler = class {
2326
2423
  this.fsw._symlinkPaths.set(absPath, targetPath);
2327
2424
  }
2328
2425
  } else if (stats.isSymbolicLink()) {
2329
- const targetPath = follow ? await fsrealpath(path12) : path12;
2426
+ const targetPath = follow ? await fsrealpath(path18) : path18;
2330
2427
  if (this.fsw.closed)
2331
2428
  return;
2332
2429
  const parent = sp.dirname(wh.watchPath);
2333
2430
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2334
2431
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2335
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2432
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2336
2433
  if (this.fsw.closed)
2337
2434
  return;
2338
2435
  if (targetPath !== void 0) {
2339
- this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2436
+ this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2340
2437
  }
2341
2438
  } else {
2342
2439
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2343
2440
  }
2344
2441
  ready();
2345
2442
  if (closer)
2346
- this.fsw._addPathCloser(path12, closer);
2443
+ this.fsw._addPathCloser(path18, closer);
2347
2444
  return false;
2348
2445
  } catch (error) {
2349
2446
  if (this.fsw._handleError(error)) {
2350
2447
  ready();
2351
- return path12;
2448
+ return path18;
2352
2449
  }
2353
2450
  }
2354
2451
  }
@@ -2391,24 +2488,24 @@ function createPattern(matcher) {
2391
2488
  }
2392
2489
  return () => false;
2393
2490
  }
2394
- function normalizePath(path12) {
2395
- if (typeof path12 !== "string")
2491
+ function normalizePath(path18) {
2492
+ if (typeof path18 !== "string")
2396
2493
  throw new Error("string expected");
2397
- path12 = sp2.normalize(path12);
2398
- path12 = path12.replace(/\\/g, "/");
2494
+ path18 = sp2.normalize(path18);
2495
+ path18 = path18.replace(/\\/g, "/");
2399
2496
  let prepend = false;
2400
- if (path12.startsWith("//"))
2497
+ if (path18.startsWith("//"))
2401
2498
  prepend = true;
2402
- path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2499
+ path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2403
2500
  if (prepend)
2404
- path12 = "/" + path12;
2405
- return path12;
2501
+ path18 = "/" + path18;
2502
+ return path18;
2406
2503
  }
2407
2504
  function matchPatterns(patterns, testString, stats) {
2408
- const path12 = normalizePath(testString);
2505
+ const path18 = normalizePath(testString);
2409
2506
  for (let index = 0; index < patterns.length; index++) {
2410
2507
  const pattern = patterns[index];
2411
- if (pattern(path12, stats)) {
2508
+ if (pattern(path18, stats)) {
2412
2509
  return true;
2413
2510
  }
2414
2511
  }
@@ -2446,19 +2543,19 @@ var toUnix = (string) => {
2446
2543
  }
2447
2544
  return str;
2448
2545
  };
2449
- var normalizePathToUnix = (path12) => toUnix(sp2.normalize(toUnix(path12)));
2450
- var normalizeIgnored = (cwd = "") => (path12) => {
2451
- if (typeof path12 === "string") {
2452
- return normalizePathToUnix(sp2.isAbsolute(path12) ? path12 : sp2.join(cwd, path12));
2546
+ var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2547
+ var normalizeIgnored = (cwd = "") => (path18) => {
2548
+ if (typeof path18 === "string") {
2549
+ return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2453
2550
  } else {
2454
- return path12;
2551
+ return path18;
2455
2552
  }
2456
2553
  };
2457
- var getAbsolutePath = (path12, cwd) => {
2458
- if (sp2.isAbsolute(path12)) {
2459
- return path12;
2554
+ var getAbsolutePath = (path18, cwd) => {
2555
+ if (sp2.isAbsolute(path18)) {
2556
+ return path18;
2460
2557
  }
2461
- return sp2.join(cwd, path12);
2558
+ return sp2.join(cwd, path18);
2462
2559
  };
2463
2560
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2464
2561
  var DirEntry = class {
@@ -2523,10 +2620,10 @@ var WatchHelper = class {
2523
2620
  dirParts;
2524
2621
  followSymlinks;
2525
2622
  statMethod;
2526
- constructor(path12, follow, fsw) {
2623
+ constructor(path18, follow, fsw) {
2527
2624
  this.fsw = fsw;
2528
- const watchPath = path12;
2529
- this.path = path12 = path12.replace(REPLACER_RE, "");
2625
+ const watchPath = path18;
2626
+ this.path = path18 = path18.replace(REPLACER_RE, "");
2530
2627
  this.watchPath = watchPath;
2531
2628
  this.fullWatchPath = sp2.resolve(watchPath);
2532
2629
  this.dirParts = [];
@@ -2666,20 +2763,20 @@ var FSWatcher = class extends EventEmitter {
2666
2763
  this._closePromise = void 0;
2667
2764
  let paths = unifyPaths(paths_);
2668
2765
  if (cwd) {
2669
- paths = paths.map((path12) => {
2670
- const absPath = getAbsolutePath(path12, cwd);
2766
+ paths = paths.map((path18) => {
2767
+ const absPath = getAbsolutePath(path18, cwd);
2671
2768
  return absPath;
2672
2769
  });
2673
2770
  }
2674
- paths.forEach((path12) => {
2675
- this._removeIgnoredPath(path12);
2771
+ paths.forEach((path18) => {
2772
+ this._removeIgnoredPath(path18);
2676
2773
  });
2677
2774
  this._userIgnored = void 0;
2678
2775
  if (!this._readyCount)
2679
2776
  this._readyCount = 0;
2680
2777
  this._readyCount += paths.length;
2681
- Promise.all(paths.map(async (path12) => {
2682
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2778
+ Promise.all(paths.map(async (path18) => {
2779
+ const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2683
2780
  if (res)
2684
2781
  this._emitReady();
2685
2782
  return res;
@@ -2701,17 +2798,17 @@ var FSWatcher = class extends EventEmitter {
2701
2798
  return this;
2702
2799
  const paths = unifyPaths(paths_);
2703
2800
  const { cwd } = this.options;
2704
- paths.forEach((path12) => {
2705
- if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2801
+ paths.forEach((path18) => {
2802
+ if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2706
2803
  if (cwd)
2707
- path12 = sp2.join(cwd, path12);
2708
- path12 = sp2.resolve(path12);
2804
+ path18 = sp2.join(cwd, path18);
2805
+ path18 = sp2.resolve(path18);
2709
2806
  }
2710
- this._closePath(path12);
2711
- this._addIgnoredPath(path12);
2712
- if (this._watched.has(path12)) {
2807
+ this._closePath(path18);
2808
+ this._addIgnoredPath(path18);
2809
+ if (this._watched.has(path18)) {
2713
2810
  this._addIgnoredPath({
2714
- path: path12,
2811
+ path: path18,
2715
2812
  recursive: true
2716
2813
  });
2717
2814
  }
@@ -2775,38 +2872,38 @@ var FSWatcher = class extends EventEmitter {
2775
2872
  * @param stats arguments to be passed with event
2776
2873
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2777
2874
  */
2778
- async _emit(event, path12, stats) {
2875
+ async _emit(event, path18, stats) {
2779
2876
  if (this.closed)
2780
2877
  return;
2781
2878
  const opts = this.options;
2782
2879
  if (isWindows)
2783
- path12 = sp2.normalize(path12);
2880
+ path18 = sp2.normalize(path18);
2784
2881
  if (opts.cwd)
2785
- path12 = sp2.relative(opts.cwd, path12);
2786
- const args = [path12];
2882
+ path18 = sp2.relative(opts.cwd, path18);
2883
+ const args = [path18];
2787
2884
  if (stats != null)
2788
2885
  args.push(stats);
2789
2886
  const awf = opts.awaitWriteFinish;
2790
2887
  let pw;
2791
- if (awf && (pw = this._pendingWrites.get(path12))) {
2888
+ if (awf && (pw = this._pendingWrites.get(path18))) {
2792
2889
  pw.lastChange = /* @__PURE__ */ new Date();
2793
2890
  return this;
2794
2891
  }
2795
2892
  if (opts.atomic) {
2796
2893
  if (event === EVENTS.UNLINK) {
2797
- this._pendingUnlinks.set(path12, [event, ...args]);
2894
+ this._pendingUnlinks.set(path18, [event, ...args]);
2798
2895
  setTimeout(() => {
2799
- this._pendingUnlinks.forEach((entry, path13) => {
2896
+ this._pendingUnlinks.forEach((entry, path19) => {
2800
2897
  this.emit(...entry);
2801
2898
  this.emit(EVENTS.ALL, ...entry);
2802
- this._pendingUnlinks.delete(path13);
2899
+ this._pendingUnlinks.delete(path19);
2803
2900
  });
2804
2901
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2805
2902
  return this;
2806
2903
  }
2807
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2904
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2808
2905
  event = EVENTS.CHANGE;
2809
- this._pendingUnlinks.delete(path12);
2906
+ this._pendingUnlinks.delete(path18);
2810
2907
  }
2811
2908
  }
2812
2909
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2824,16 +2921,16 @@ var FSWatcher = class extends EventEmitter {
2824
2921
  this.emitWithAll(event, args);
2825
2922
  }
2826
2923
  };
2827
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2924
+ this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2828
2925
  return this;
2829
2926
  }
2830
2927
  if (event === EVENTS.CHANGE) {
2831
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2928
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2832
2929
  if (isThrottled)
2833
2930
  return this;
2834
2931
  }
2835
2932
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2836
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path12) : path12;
2933
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2837
2934
  let stats2;
2838
2935
  try {
2839
2936
  stats2 = await stat3(fullPath);
@@ -2864,23 +2961,23 @@ var FSWatcher = class extends EventEmitter {
2864
2961
  * @param timeout duration of time to suppress duplicate actions
2865
2962
  * @returns tracking object or false if action should be suppressed
2866
2963
  */
2867
- _throttle(actionType, path12, timeout) {
2964
+ _throttle(actionType, path18, timeout) {
2868
2965
  if (!this._throttled.has(actionType)) {
2869
2966
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2870
2967
  }
2871
2968
  const action = this._throttled.get(actionType);
2872
2969
  if (!action)
2873
2970
  throw new Error("invalid throttle");
2874
- const actionPath = action.get(path12);
2971
+ const actionPath = action.get(path18);
2875
2972
  if (actionPath) {
2876
2973
  actionPath.count++;
2877
2974
  return false;
2878
2975
  }
2879
2976
  let timeoutObject;
2880
2977
  const clear = () => {
2881
- const item = action.get(path12);
2978
+ const item = action.get(path18);
2882
2979
  const count = item ? item.count : 0;
2883
- action.delete(path12);
2980
+ action.delete(path18);
2884
2981
  clearTimeout(timeoutObject);
2885
2982
  if (item)
2886
2983
  clearTimeout(item.timeoutObject);
@@ -2888,7 +2985,7 @@ var FSWatcher = class extends EventEmitter {
2888
2985
  };
2889
2986
  timeoutObject = setTimeout(clear, timeout);
2890
2987
  const thr = { timeoutObject, clear, count: 0 };
2891
- action.set(path12, thr);
2988
+ action.set(path18, thr);
2892
2989
  return thr;
2893
2990
  }
2894
2991
  _incrReadyCount() {
@@ -2902,44 +2999,44 @@ var FSWatcher = class extends EventEmitter {
2902
2999
  * @param event
2903
3000
  * @param awfEmit Callback to be called when ready for event to be emitted.
2904
3001
  */
2905
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
3002
+ _awaitWriteFinish(path18, threshold, event, awfEmit) {
2906
3003
  const awf = this.options.awaitWriteFinish;
2907
3004
  if (typeof awf !== "object")
2908
3005
  return;
2909
3006
  const pollInterval = awf.pollInterval;
2910
3007
  let timeoutHandler;
2911
- let fullPath = path12;
2912
- if (this.options.cwd && !sp2.isAbsolute(path12)) {
2913
- fullPath = sp2.join(this.options.cwd, path12);
3008
+ let fullPath = path18;
3009
+ if (this.options.cwd && !sp2.isAbsolute(path18)) {
3010
+ fullPath = sp2.join(this.options.cwd, path18);
2914
3011
  }
2915
3012
  const now = /* @__PURE__ */ new Date();
2916
3013
  const writes = this._pendingWrites;
2917
3014
  function awaitWriteFinishFn(prevStat) {
2918
3015
  statcb(fullPath, (err, curStat) => {
2919
- if (err || !writes.has(path12)) {
3016
+ if (err || !writes.has(path18)) {
2920
3017
  if (err && err.code !== "ENOENT")
2921
3018
  awfEmit(err);
2922
3019
  return;
2923
3020
  }
2924
3021
  const now2 = Number(/* @__PURE__ */ new Date());
2925
3022
  if (prevStat && curStat.size !== prevStat.size) {
2926
- writes.get(path12).lastChange = now2;
3023
+ writes.get(path18).lastChange = now2;
2927
3024
  }
2928
- const pw = writes.get(path12);
3025
+ const pw = writes.get(path18);
2929
3026
  const df = now2 - pw.lastChange;
2930
3027
  if (df >= threshold) {
2931
- writes.delete(path12);
3028
+ writes.delete(path18);
2932
3029
  awfEmit(void 0, curStat);
2933
3030
  } else {
2934
3031
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2935
3032
  }
2936
3033
  });
2937
3034
  }
2938
- if (!writes.has(path12)) {
2939
- writes.set(path12, {
3035
+ if (!writes.has(path18)) {
3036
+ writes.set(path18, {
2940
3037
  lastChange: now,
2941
3038
  cancelWait: () => {
2942
- writes.delete(path12);
3039
+ writes.delete(path18);
2943
3040
  clearTimeout(timeoutHandler);
2944
3041
  return event;
2945
3042
  }
@@ -2950,8 +3047,8 @@ var FSWatcher = class extends EventEmitter {
2950
3047
  /**
2951
3048
  * Determines whether user has asked to ignore this path.
2952
3049
  */
2953
- _isIgnored(path12, stats) {
2954
- if (this.options.atomic && DOT_RE.test(path12))
3050
+ _isIgnored(path18, stats) {
3051
+ if (this.options.atomic && DOT_RE.test(path18))
2955
3052
  return true;
2956
3053
  if (!this._userIgnored) {
2957
3054
  const { cwd } = this.options;
@@ -2961,17 +3058,17 @@ var FSWatcher = class extends EventEmitter {
2961
3058
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2962
3059
  this._userIgnored = anymatch(list, void 0);
2963
3060
  }
2964
- return this._userIgnored(path12, stats);
3061
+ return this._userIgnored(path18, stats);
2965
3062
  }
2966
- _isntIgnored(path12, stat4) {
2967
- return !this._isIgnored(path12, stat4);
3063
+ _isntIgnored(path18, stat4) {
3064
+ return !this._isIgnored(path18, stat4);
2968
3065
  }
2969
3066
  /**
2970
3067
  * Provides a set of common helpers and properties relating to symlink handling.
2971
3068
  * @param path file or directory pattern being watched
2972
3069
  */
2973
- _getWatchHelpers(path12) {
2974
- return new WatchHelper(path12, this.options.followSymlinks, this);
3070
+ _getWatchHelpers(path18) {
3071
+ return new WatchHelper(path18, this.options.followSymlinks, this);
2975
3072
  }
2976
3073
  // Directory helpers
2977
3074
  // -----------------
@@ -3003,63 +3100,63 @@ var FSWatcher = class extends EventEmitter {
3003
3100
  * @param item base path of item/directory
3004
3101
  */
3005
3102
  _remove(directory, item, isDirectory) {
3006
- const path12 = sp2.join(directory, item);
3007
- const fullPath = sp2.resolve(path12);
3008
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path12) || this._watched.has(fullPath);
3009
- if (!this._throttle("remove", path12, 100))
3103
+ const path18 = sp2.join(directory, item);
3104
+ const fullPath = sp2.resolve(path18);
3105
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3106
+ if (!this._throttle("remove", path18, 100))
3010
3107
  return;
3011
3108
  if (!isDirectory && this._watched.size === 1) {
3012
3109
  this.add(directory, item, true);
3013
3110
  }
3014
- const wp = this._getWatchedDir(path12);
3111
+ const wp = this._getWatchedDir(path18);
3015
3112
  const nestedDirectoryChildren = wp.getChildren();
3016
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
3113
+ nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3017
3114
  const parent = this._getWatchedDir(directory);
3018
3115
  const wasTracked = parent.has(item);
3019
3116
  parent.remove(item);
3020
3117
  if (this._symlinkPaths.has(fullPath)) {
3021
3118
  this._symlinkPaths.delete(fullPath);
3022
3119
  }
3023
- let relPath = path12;
3120
+ let relPath = path18;
3024
3121
  if (this.options.cwd)
3025
- relPath = sp2.relative(this.options.cwd, path12);
3122
+ relPath = sp2.relative(this.options.cwd, path18);
3026
3123
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3027
3124
  const event = this._pendingWrites.get(relPath).cancelWait();
3028
3125
  if (event === EVENTS.ADD)
3029
3126
  return;
3030
3127
  }
3031
- this._watched.delete(path12);
3128
+ this._watched.delete(path18);
3032
3129
  this._watched.delete(fullPath);
3033
3130
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3034
- if (wasTracked && !this._isIgnored(path12))
3035
- this._emit(eventName, path12);
3036
- this._closePath(path12);
3131
+ if (wasTracked && !this._isIgnored(path18))
3132
+ this._emit(eventName, path18);
3133
+ this._closePath(path18);
3037
3134
  }
3038
3135
  /**
3039
3136
  * Closes all watchers for a path
3040
3137
  */
3041
- _closePath(path12) {
3042
- this._closeFile(path12);
3043
- const dir = sp2.dirname(path12);
3044
- this._getWatchedDir(dir).remove(sp2.basename(path12));
3138
+ _closePath(path18) {
3139
+ this._closeFile(path18);
3140
+ const dir = sp2.dirname(path18);
3141
+ this._getWatchedDir(dir).remove(sp2.basename(path18));
3045
3142
  }
3046
3143
  /**
3047
3144
  * Closes only file-specific watchers
3048
3145
  */
3049
- _closeFile(path12) {
3050
- const closers = this._closers.get(path12);
3146
+ _closeFile(path18) {
3147
+ const closers = this._closers.get(path18);
3051
3148
  if (!closers)
3052
3149
  return;
3053
3150
  closers.forEach((closer) => closer());
3054
- this._closers.delete(path12);
3151
+ this._closers.delete(path18);
3055
3152
  }
3056
- _addPathCloser(path12, closer) {
3153
+ _addPathCloser(path18, closer) {
3057
3154
  if (!closer)
3058
3155
  return;
3059
- let list = this._closers.get(path12);
3156
+ let list = this._closers.get(path18);
3060
3157
  if (!list) {
3061
3158
  list = [];
3062
- this._closers.set(path12, list);
3159
+ this._closers.set(path18, list);
3063
3160
  }
3064
3161
  list.push(closer);
3065
3162
  }
@@ -3088,13 +3185,13 @@ function watch(paths, options = {}) {
3088
3185
  }
3089
3186
  var chokidar_default = { watch, FSWatcher };
3090
3187
 
3091
- // src/watcher/index.ts
3092
- import * as path5 from "path";
3188
+ // src/watcher/file-watcher.ts
3189
+ import * as path8 from "path";
3093
3190
 
3094
3191
  // src/utils/files.ts
3095
3192
  var import_ignore = __toESM(require_ignore(), 1);
3096
- import { existsSync as existsSync4, readFileSync as readFileSync3, promises as fsPromises } from "fs";
3097
- import * as path4 from "path";
3193
+ import { existsSync as existsSync5, readFileSync as readFileSync4, promises as fsPromises } from "fs";
3194
+ import * as path7 from "path";
3098
3195
  var PROJECT_MARKERS = [
3099
3196
  ".git",
3100
3197
  "package.json",
@@ -3113,7 +3210,7 @@ var PROJECT_MARKERS = [
3113
3210
  ];
3114
3211
  function hasProjectMarker(projectRoot) {
3115
3212
  for (const marker of PROJECT_MARKERS) {
3116
- if (existsSync4(path4.join(projectRoot, marker))) {
3213
+ if (existsSync5(path7.join(projectRoot, marker))) {
3117
3214
  return true;
3118
3215
  }
3119
3216
  }
@@ -3139,23 +3236,17 @@ function createIgnoreFilter(projectRoot) {
3139
3236
  "**/*build*/**"
3140
3237
  ];
3141
3238
  ig.add(defaultIgnores);
3142
- const gitignorePath = path4.join(projectRoot, ".gitignore");
3143
- if (existsSync4(gitignorePath)) {
3144
- const gitignoreContent = readFileSync3(gitignorePath, "utf-8");
3239
+ const gitignorePath = path7.join(projectRoot, ".gitignore");
3240
+ if (existsSync5(gitignorePath)) {
3241
+ const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
3145
3242
  ig.add(gitignoreContent);
3146
3243
  }
3147
3244
  return ig;
3148
3245
  }
3149
3246
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3150
- const relativePath = path4.relative(projectRoot, filePath);
3151
- const pathParts = relativePath.split(path4.sep);
3152
- for (const part of pathParts) {
3153
- if (part.startsWith(".") && part !== "." && part !== "..") {
3154
- return false;
3155
- }
3156
- if (part.toLowerCase().includes("build")) {
3157
- return false;
3158
- }
3247
+ const relativePath = path7.relative(projectRoot, filePath);
3248
+ if (hasFilteredPathSegment(relativePath, path7.sep)) {
3249
+ return false;
3159
3250
  }
3160
3251
  if (ignoreFilter.ignores(relativePath)) {
3161
3252
  return false;
@@ -3192,15 +3283,15 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3192
3283
  const filesInDir = [];
3193
3284
  const subdirs = [];
3194
3285
  for (const entry of entries) {
3195
- const fullPath = path4.join(dir, entry.name);
3196
- const relativePath = path4.relative(projectRoot, fullPath);
3197
- if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
3286
+ const fullPath = path7.join(dir, entry.name);
3287
+ const relativePath = path7.relative(projectRoot, fullPath);
3288
+ if (isHiddenPathSegment(entry.name)) {
3198
3289
  if (entry.isDirectory()) {
3199
3290
  skipped.push({ path: relativePath, reason: "excluded" });
3200
3291
  }
3201
3292
  continue;
3202
3293
  }
3203
- if (entry.isDirectory() && entry.name.toLowerCase().includes("build")) {
3294
+ if (entry.isDirectory() && isBuildPathSegment(entry.name)) {
3204
3295
  skipped.push({ path: relativePath, reason: "excluded" });
3205
3296
  continue;
3206
3297
  }
@@ -3242,7 +3333,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3242
3333
  yield f;
3243
3334
  }
3244
3335
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3245
- skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3336
+ skipped.push({ path: path7.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3246
3337
  }
3247
3338
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3248
3339
  if (canRecurse) {
@@ -3282,8 +3373,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3282
3373
  if (additionalRoots && additionalRoots.length > 0) {
3283
3374
  const normalizedRoots = /* @__PURE__ */ new Set();
3284
3375
  for (const kbRoot of additionalRoots) {
3285
- const resolved = path4.normalize(
3286
- path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3376
+ const resolved = path7.normalize(
3377
+ path7.isAbsolute(kbRoot) ? kbRoot : path7.resolve(projectRoot, kbRoot)
3287
3378
  );
3288
3379
  normalizedRoots.add(resolved);
3289
3380
  }
@@ -3316,7 +3407,7 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3316
3407
  return { files, skipped };
3317
3408
  }
3318
3409
 
3319
- // src/watcher/index.ts
3410
+ // src/watcher/file-watcher.ts
3320
3411
  var FileWatcher = class {
3321
3412
  watcher = null;
3322
3413
  projectRoot;
@@ -3337,16 +3428,13 @@ var FileWatcher = class {
3337
3428
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3338
3429
  this.watcher = chokidar_default.watch(this.projectRoot, {
3339
3430
  ignored: (filePath) => {
3340
- const relativePath = path5.relative(this.projectRoot, filePath);
3431
+ const relativePath = path8.relative(this.projectRoot, filePath);
3341
3432
  if (!relativePath) return false;
3342
- const pathParts = relativePath.split(path5.sep);
3343
- for (const part of pathParts) {
3344
- if (part.startsWith(".") && part !== "." && part !== "..") {
3345
- return true;
3346
- }
3347
- if (part.toLowerCase().includes("build")) {
3348
- return true;
3349
- }
3433
+ if (hasFilteredPathSegment(relativePath, path8.sep)) {
3434
+ return true;
3435
+ }
3436
+ if (isRestrictedDirectory(relativePath, path8.sep)) {
3437
+ return true;
3350
3438
  }
3351
3439
  if (ignoreFilter.ignores(relativePath)) {
3352
3440
  return true;
@@ -3360,6 +3448,13 @@ var FileWatcher = class {
3360
3448
  pollInterval: 100
3361
3449
  }
3362
3450
  });
3451
+ this.watcher.on("error", (error) => {
3452
+ const err = error instanceof Error ? error : null;
3453
+ if (err?.code === "EPERM" || err?.code === "EACCES") {
3454
+ return;
3455
+ }
3456
+ console.error("[codebase-index] Watcher error:", err?.message ?? error);
3457
+ });
3363
3458
  this.watcher.on("add", (filePath) => this.handleChange("add", filePath));
3364
3459
  this.watcher.on("change", (filePath) => this.handleChange("change", filePath));
3365
3460
  this.watcher.on("unlink", (filePath) => this.handleChange("unlink", filePath));
@@ -3391,7 +3486,7 @@ var FileWatcher = class {
3391
3486
  return;
3392
3487
  }
3393
3488
  const changes = Array.from(this.pendingChanges.entries()).map(
3394
- ([path12, type]) => ({ path: path12, type })
3489
+ ([path18, type]) => ({ path: path18, type })
3395
3490
  );
3396
3491
  this.pendingChanges.clear();
3397
3492
  try {
@@ -3416,6 +3511,9 @@ var FileWatcher = class {
3416
3511
  return this.watcher !== null;
3417
3512
  }
3418
3513
  };
3514
+
3515
+ // src/watcher/git-head-watcher.ts
3516
+ import * as path9 from "path";
3419
3517
  var GitHeadWatcher = class {
3420
3518
  watcher = null;
3421
3519
  projectRoot;
@@ -3437,7 +3535,7 @@ var GitHeadWatcher = class {
3437
3535
  this.onBranchChange = handler;
3438
3536
  this.currentBranch = getCurrentBranch(this.projectRoot);
3439
3537
  const headPath = getHeadPath(this.projectRoot);
3440
- const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3538
+ const refsPath = path9.join(this.projectRoot, ".git", "refs", "heads");
3441
3539
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3442
3540
  persistent: true,
3443
3541
  ignoreInitial: true,
@@ -3489,7 +3587,9 @@ var GitHeadWatcher = class {
3489
3587
  return this.watcher !== null;
3490
3588
  }
3491
3589
  };
3492
- function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3590
+
3591
+ // src/watcher/index.ts
3592
+ function createWatcherWithIndexer(getIndexer, projectRoot, config) {
3493
3593
  const fileWatcher = new FileWatcher(projectRoot, config);
3494
3594
  fileWatcher.start(async (changes) => {
3495
3595
  const hasAddOrChange = changes.some(
@@ -3497,7 +3597,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3497
3597
  );
3498
3598
  const hasDelete = changes.some((c) => c.type === "unlink");
3499
3599
  if (hasAddOrChange || hasDelete) {
3500
- await getIndexer2().index();
3600
+ await getIndexer().index();
3501
3601
  }
3502
3602
  });
3503
3603
  let gitWatcher = null;
@@ -3505,7 +3605,7 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3505
3605
  gitWatcher = new GitHeadWatcher(projectRoot);
3506
3606
  gitWatcher.start(async (oldBranch, newBranch) => {
3507
3607
  console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
3508
- await getIndexer2().index();
3608
+ await getIndexer().index();
3509
3609
  });
3510
3610
  }
3511
3611
  return {
@@ -3522,8 +3622,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3522
3622
  import { tool } from "@opencode-ai/plugin";
3523
3623
 
3524
3624
  // src/indexer/index.ts
3525
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
3526
- import * as path8 from "path";
3625
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
3626
+ import * as path12 from "path";
3527
3627
  import { performance as performance2 } from "perf_hooks";
3528
3628
 
3529
3629
  // node_modules/eventemitter3/index.mjs
@@ -3548,7 +3648,7 @@ function pTimeout(promise, options) {
3548
3648
  } = options;
3549
3649
  let timer;
3550
3650
  let abortHandler;
3551
- const wrappedPromise = new Promise((resolve9, reject) => {
3651
+ const wrappedPromise = new Promise((resolve12, reject) => {
3552
3652
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3553
3653
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3554
3654
  }
@@ -3562,7 +3662,7 @@ function pTimeout(promise, options) {
3562
3662
  };
3563
3663
  signal.addEventListener("abort", abortHandler, { once: true });
3564
3664
  }
3565
- promise.then(resolve9, reject);
3665
+ promise.then(resolve12, reject);
3566
3666
  if (milliseconds === Number.POSITIVE_INFINITY) {
3567
3667
  return;
3568
3668
  }
@@ -3570,7 +3670,7 @@ function pTimeout(promise, options) {
3570
3670
  timer = customTimers.setTimeout.call(void 0, () => {
3571
3671
  if (fallback) {
3572
3672
  try {
3573
- resolve9(fallback());
3673
+ resolve12(fallback());
3574
3674
  } catch (error) {
3575
3675
  reject(error);
3576
3676
  }
@@ -3580,7 +3680,7 @@ function pTimeout(promise, options) {
3580
3680
  promise.cancel();
3581
3681
  }
3582
3682
  if (message === false) {
3583
- resolve9();
3683
+ resolve12();
3584
3684
  } else if (message instanceof Error) {
3585
3685
  reject(message);
3586
3686
  } else {
@@ -3982,7 +4082,7 @@ var PQueue = class extends import_index.default {
3982
4082
  // Assign unique ID if not provided
3983
4083
  id: options.id ?? (this.#idAssigner++).toString()
3984
4084
  };
3985
- return new Promise((resolve9, reject) => {
4085
+ return new Promise((resolve12, reject) => {
3986
4086
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3987
4087
  let cleanupQueueAbortHandler = () => void 0;
3988
4088
  const run = async () => {
@@ -4022,7 +4122,7 @@ var PQueue = class extends import_index.default {
4022
4122
  })]);
4023
4123
  }
4024
4124
  const result = await operation;
4025
- resolve9(result);
4125
+ resolve12(result);
4026
4126
  this.emit("completed", result);
4027
4127
  } catch (error) {
4028
4128
  reject(error);
@@ -4210,13 +4310,13 @@ var PQueue = class extends import_index.default {
4210
4310
  });
4211
4311
  }
4212
4312
  async #onEvent(event, filter) {
4213
- return new Promise((resolve9) => {
4313
+ return new Promise((resolve12) => {
4214
4314
  const listener = () => {
4215
4315
  if (filter && !filter()) {
4216
4316
  return;
4217
4317
  }
4218
4318
  this.off(event, listener);
4219
- resolve9();
4319
+ resolve12();
4220
4320
  };
4221
4321
  this.on(event, listener);
4222
4322
  });
@@ -4502,7 +4602,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4502
4602
  const finalDelay = Math.min(delayTime, remainingTime);
4503
4603
  options.signal?.throwIfAborted();
4504
4604
  if (finalDelay > 0) {
4505
- await new Promise((resolve9, reject) => {
4605
+ await new Promise((resolve12, reject) => {
4506
4606
  const onAbort = () => {
4507
4607
  clearTimeout(timeoutToken);
4508
4608
  options.signal?.removeEventListener("abort", onAbort);
@@ -4510,7 +4610,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4510
4610
  };
4511
4611
  const timeoutToken = setTimeout(() => {
4512
4612
  options.signal?.removeEventListener("abort", onAbort);
4513
- resolve9();
4613
+ resolve12();
4514
4614
  }, finalDelay);
4515
4615
  if (options.unref) {
4516
4616
  timeoutToken.unref?.();
@@ -4571,17 +4671,17 @@ async function pRetry(input, options = {}) {
4571
4671
  }
4572
4672
 
4573
4673
  // src/embeddings/detector.ts
4574
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4575
- import * as path6 from "path";
4674
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4675
+ import * as path10 from "path";
4576
4676
  import * as os3 from "os";
4577
4677
  function getOpenCodeAuthPath() {
4578
- return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4678
+ return path10.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4579
4679
  }
4580
4680
  function loadOpenCodeAuth() {
4581
4681
  const authPath = getOpenCodeAuthPath();
4582
4682
  try {
4583
- if (existsSync5(authPath)) {
4584
- return JSON.parse(readFileSync4(authPath, "utf-8"));
4683
+ if (existsSync6(authPath)) {
4684
+ return JSON.parse(readFileSync5(authPath, "utf-8"));
4585
4685
  }
4586
4686
  } catch {
4587
4687
  }
@@ -4648,7 +4748,8 @@ function getGitHubCopilotCredentials() {
4648
4748
  if (!copilotAuth || copilotAuth.type !== "oauth") {
4649
4749
  return null;
4650
4750
  }
4651
- const baseUrl = copilotAuth.enterpriseUrl ? `https://copilot-api.${copilotAuth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4751
+ const auth = copilotAuth;
4752
+ const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4652
4753
  return {
4653
4754
  provider: "github-copilot",
4654
4755
  baseUrl,
@@ -4744,42 +4845,12 @@ function createCustomProviderInfo(config) {
4744
4845
  };
4745
4846
  }
4746
4847
 
4747
- // src/embeddings/provider.ts
4748
- var CustomProviderNonRetryableError = class extends Error {
4749
- constructor(message) {
4750
- super(message);
4751
- this.name = "CustomProviderNonRetryableError";
4752
- }
4753
- };
4754
- function createEmbeddingProvider(configuredProviderInfo) {
4755
- switch (configuredProviderInfo.provider) {
4756
- case "github-copilot":
4757
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4758
- case "openai":
4759
- return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4760
- case "google":
4761
- return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4762
- case "ollama":
4763
- return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4764
- case "custom":
4765
- return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4766
- default: {
4767
- const _exhaustive = configuredProviderInfo;
4768
- throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
4769
- }
4770
- }
4771
- }
4772
- var GitHubCopilotEmbeddingProvider = class {
4848
+ // src/embeddings/provider-types.ts
4849
+ var BaseEmbeddingProvider = class {
4773
4850
  constructor(credentials, modelInfo) {
4774
4851
  this.credentials = credentials;
4775
4852
  this.modelInfo = modelInfo;
4776
4853
  }
4777
- getToken() {
4778
- if (!this.credentials.refreshToken) {
4779
- throw new Error("No OAuth token available for GitHub");
4780
- }
4781
- return this.credentials.refreshToken;
4782
- }
4783
4854
  async embedQuery(query) {
4784
4855
  const result = await this.embedBatch([query]);
4785
4856
  return {
@@ -4794,69 +4865,204 @@ var GitHubCopilotEmbeddingProvider = class {
4794
4865
  tokensUsed: result.totalTokensUsed
4795
4866
  };
4796
4867
  }
4797
- async embedBatch(texts) {
4798
- const token = this.getToken();
4799
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4800
- method: "POST",
4801
- headers: {
4802
- Authorization: `Bearer ${token}`,
4803
- "Content-Type": "application/json",
4804
- Accept: "application/vnd.github+json",
4805
- "X-GitHub-Api-Version": "2022-11-28"
4806
- },
4807
- body: JSON.stringify({
4808
- model: `openai/${this.modelInfo.model}`,
4809
- input: texts
4810
- })
4811
- });
4812
- if (!response.ok) {
4813
- const error = await response.text();
4814
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4815
- }
4816
- const data = await response.json();
4817
- return {
4818
- embeddings: data.data.map((d) => d.embedding),
4819
- totalTokensUsed: data.usage.total_tokens
4820
- };
4821
- }
4822
4868
  getModelInfo() {
4823
4869
  return this.modelInfo;
4824
4870
  }
4825
4871
  };
4826
- var OpenAIEmbeddingProvider = class {
4872
+ var CustomProviderNonRetryableError = class extends Error {
4873
+ constructor(message) {
4874
+ super(message);
4875
+ this.name = "CustomProviderNonRetryableError";
4876
+ }
4877
+ };
4878
+
4879
+ // src/utils/url-validation.ts
4880
+ var BLOCKED_METADATA_IPS = [
4881
+ /^169\.254\.169\.254$/,
4882
+ // AWS/Azure/GCP metadata
4883
+ /^169\.254\.170\.2$/,
4884
+ // AWS ECS task metadata
4885
+ /^fd00:ec2::254$/
4886
+ // AWS IMDSv2 IPv6
4887
+ ];
4888
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
4889
+ "metadata.google.internal",
4890
+ "metadata.google",
4891
+ "metadata.goog",
4892
+ "kubernetes.default.svc"
4893
+ ]);
4894
+ function validateExternalUrl(urlString) {
4895
+ let parsed;
4896
+ try {
4897
+ parsed = new URL(urlString);
4898
+ } catch {
4899
+ return { valid: false, reason: `Invalid URL: ${sanitizeUrlForError(urlString)}` };
4900
+ }
4901
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4902
+ return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
4903
+ }
4904
+ const hostname = parsed.hostname.toLowerCase();
4905
+ if (BLOCKED_HOSTNAMES.has(hostname)) {
4906
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
4907
+ }
4908
+ for (const pattern of BLOCKED_METADATA_IPS) {
4909
+ if (pattern.test(hostname)) {
4910
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
4911
+ }
4912
+ }
4913
+ if (/^169\.254\./.test(hostname)) {
4914
+ return { valid: false, reason: `Blocked: link-local address (${hostname})` };
4915
+ }
4916
+ return { valid: true };
4917
+ }
4918
+ function sanitizeUrlForError(url) {
4919
+ try {
4920
+ const parsed = new URL(url);
4921
+ parsed.username = "";
4922
+ parsed.password = "";
4923
+ return parsed.toString();
4924
+ } catch {
4925
+ const maxLen = 80;
4926
+ if (url.length > maxLen) {
4927
+ return url.slice(0, maxLen) + "...";
4928
+ }
4929
+ return url;
4930
+ }
4931
+ }
4932
+
4933
+ // src/embeddings/providers/custom.ts
4934
+ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
4827
4935
  constructor(credentials, modelInfo) {
4828
- this.credentials = credentials;
4829
- this.modelInfo = modelInfo;
4936
+ super(credentials, modelInfo);
4830
4937
  }
4831
- async embedQuery(query) {
4832
- const result = await this.embedBatch([query]);
4833
- return {
4834
- embedding: result.embeddings[0],
4835
- tokensUsed: result.totalTokensUsed
4938
+ splitIntoRequestBatches(texts) {
4939
+ const maxBatchSize = this.modelInfo.maxBatchSize;
4940
+ if (!maxBatchSize || texts.length <= maxBatchSize) {
4941
+ return [texts];
4942
+ }
4943
+ const batches = [];
4944
+ for (let i = 0; i < texts.length; i += maxBatchSize) {
4945
+ batches.push(texts.slice(i, i + maxBatchSize));
4946
+ }
4947
+ return batches;
4948
+ }
4949
+ async embedRequest(texts) {
4950
+ if (texts.length === 0) {
4951
+ return {
4952
+ embeddings: [],
4953
+ totalTokensUsed: 0
4954
+ };
4955
+ }
4956
+ const headers = {
4957
+ "Content-Type": "application/json"
4836
4958
  };
4959
+ if (this.credentials.apiKey) {
4960
+ headers.Authorization = `Bearer ${this.credentials.apiKey}`;
4961
+ }
4962
+ const baseUrl = this.credentials.baseUrl ?? "";
4963
+ const fullUrl = `${baseUrl}/embeddings`;
4964
+ const urlCheck = validateExternalUrl(fullUrl);
4965
+ if (!urlCheck.valid) {
4966
+ throw new CustomProviderNonRetryableError(
4967
+ `Custom embedding provider URL blocked (SSRF protection): ${urlCheck.reason}`
4968
+ );
4969
+ }
4970
+ const timeoutMs = this.modelInfo.timeoutMs;
4971
+ const controller = new AbortController();
4972
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
4973
+ let response;
4974
+ try {
4975
+ response = await fetch(fullUrl, {
4976
+ method: "POST",
4977
+ headers,
4978
+ body: JSON.stringify({
4979
+ model: this.modelInfo.model,
4980
+ input: texts
4981
+ }),
4982
+ signal: controller.signal
4983
+ });
4984
+ } catch (error) {
4985
+ if (error instanceof Error && error.name === "AbortError") {
4986
+ throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${sanitizeUrlForError(fullUrl)}`);
4987
+ }
4988
+ throw error;
4989
+ } finally {
4990
+ clearTimeout(timeout);
4991
+ }
4992
+ if (!response.ok) {
4993
+ const errorText = (await response.text()).slice(0, 500);
4994
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4995
+ throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
4996
+ }
4997
+ throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
4998
+ }
4999
+ const data = await response.json();
5000
+ if (data.data && Array.isArray(data.data)) {
5001
+ if (data.data.length > 0) {
5002
+ const actualDims = data.data[0].embedding.length;
5003
+ if (actualDims !== this.modelInfo.dimensions) {
5004
+ throw new Error(
5005
+ `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.`
5006
+ );
5007
+ }
5008
+ }
5009
+ if (data.data.length !== texts.length) {
5010
+ throw new Error(
5011
+ `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
5012
+ );
5013
+ }
5014
+ return {
5015
+ embeddings: data.data.map((d) => d.embedding),
5016
+ totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
5017
+ };
5018
+ }
5019
+ throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
4837
5020
  }
4838
- async embedDocument(document) {
4839
- const result = await this.embedBatch([document]);
5021
+ async embedBatch(texts) {
5022
+ const requestBatches = this.splitIntoRequestBatches(texts);
5023
+ const embeddings = [];
5024
+ let totalTokensUsed = 0;
5025
+ for (const batch of requestBatches) {
5026
+ const result = await this.embedRequest(batch);
5027
+ embeddings.push(...result.embeddings);
5028
+ totalTokensUsed += result.totalTokensUsed;
5029
+ }
4840
5030
  return {
4841
- embedding: result.embeddings[0],
4842
- tokensUsed: result.totalTokensUsed
5031
+ embeddings,
5032
+ totalTokensUsed
4843
5033
  };
4844
5034
  }
5035
+ };
5036
+
5037
+ // src/embeddings/providers/github-copilot.ts
5038
+ var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
5039
+ constructor(credentials, modelInfo) {
5040
+ super(credentials, modelInfo);
5041
+ }
5042
+ getToken() {
5043
+ if (!this.credentials.refreshToken) {
5044
+ throw new Error("No OAuth token available for GitHub");
5045
+ }
5046
+ return this.credentials.refreshToken;
5047
+ }
4845
5048
  async embedBatch(texts) {
4846
- const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5049
+ const token = this.getToken();
5050
+ const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4847
5051
  method: "POST",
4848
5052
  headers: {
4849
- Authorization: `Bearer ${this.credentials.apiKey}`,
4850
- "Content-Type": "application/json"
5053
+ Authorization: `Bearer ${token}`,
5054
+ "Content-Type": "application/json",
5055
+ Accept: "application/vnd.github+json",
5056
+ "X-GitHub-Api-Version": "2022-11-28"
4851
5057
  },
4852
5058
  body: JSON.stringify({
4853
- model: this.modelInfo.model,
5059
+ model: `openai/${this.modelInfo.model}`,
4854
5060
  input: texts
4855
5061
  })
4856
5062
  });
4857
5063
  if (!response.ok) {
4858
- const error = await response.text();
4859
- throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5064
+ const error = (await response.text()).slice(0, 500);
5065
+ throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4860
5066
  }
4861
5067
  const data = await response.json();
4862
5068
  return {
@@ -4864,16 +5070,14 @@ var OpenAIEmbeddingProvider = class {
4864
5070
  totalTokensUsed: data.usage.total_tokens
4865
5071
  };
4866
5072
  }
4867
- getModelInfo() {
4868
- return this.modelInfo;
4869
- }
4870
5073
  };
4871
- var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
5074
+
5075
+ // src/embeddings/providers/google.ts
5076
+ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
5077
+ static BATCH_SIZE = 20;
4872
5078
  constructor(credentials, modelInfo) {
4873
- this.credentials = credentials;
4874
- this.modelInfo = modelInfo;
5079
+ super(credentials, modelInfo);
4875
5080
  }
4876
- static BATCH_SIZE = 20;
4877
5081
  async embedQuery(query) {
4878
5082
  const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
4879
5083
  const result = await this.embedWithTaskType([query], taskType);
@@ -4894,12 +5098,6 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4894
5098
  const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
4895
5099
  return this.embedWithTaskType(texts, taskType);
4896
5100
  }
4897
- /**
4898
- * Embeds texts using the Google embedContent API.
4899
- * Sends multiple texts as parts in batched requests (up to BATCH_SIZE per call).
4900
- * When taskType is provided (gemini-embedding-001), includes it in the request
4901
- * for task-specific embedding optimization.
4902
- */
4903
5101
  async embedWithTaskType(texts, taskType) {
4904
5102
  const batches = [];
4905
5103
  for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
@@ -4916,17 +5114,18 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4916
5114
  outputDimensionality: this.modelInfo.dimensions
4917
5115
  }));
4918
5116
  const response = await fetch(
4919
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents?key=${this.credentials.apiKey}`,
5117
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents`,
4920
5118
  {
4921
5119
  method: "POST",
4922
5120
  headers: {
4923
- "Content-Type": "application/json"
5121
+ "Content-Type": "application/json",
5122
+ ...this.credentials.apiKey && { "x-goog-api-key": this.credentials.apiKey }
4924
5123
  },
4925
5124
  body: JSON.stringify({ requests })
4926
5125
  }
4927
5126
  );
4928
5127
  if (!response.ok) {
4929
- const error = await response.text();
5128
+ const error = (await response.text()).slice(0, 500);
4930
5129
  throw new Error(`Google embedding API error: ${response.status} - ${error}`);
4931
5130
  }
4932
5131
  const data = await response.json();
@@ -4941,29 +5140,13 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4941
5140
  totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
4942
5141
  };
4943
5142
  }
4944
- getModelInfo() {
4945
- return this.modelInfo;
4946
- }
4947
5143
  };
4948
- var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4949
- constructor(credentials, modelInfo) {
4950
- this.credentials = credentials;
4951
- this.modelInfo = modelInfo;
4952
- }
5144
+
5145
+ // src/embeddings/providers/ollama.ts
5146
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
4953
5147
  static MIN_TRUNCATION_CHARS = 512;
4954
- async embedQuery(query) {
4955
- const result = await this.embedBatch([query]);
4956
- return {
4957
- embedding: result.embeddings[0],
4958
- tokensUsed: result.totalTokensUsed
4959
- };
4960
- }
4961
- async embedDocument(document) {
4962
- const result = await this.embedBatch([document]);
4963
- return {
4964
- embedding: result.embeddings[0],
4965
- tokensUsed: result.totalTokensUsed
4966
- };
5148
+ constructor(credentials, modelInfo) {
5149
+ super(credentials, modelInfo);
4967
5150
  }
4968
5151
  estimateTokens(text) {
4969
5152
  return Math.ceil(text.length / 4);
@@ -5027,166 +5210,97 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
5027
5210
  return await this.embedSingle(truncated);
5028
5211
  } catch (retryError) {
5029
5212
  if (!this.isContextLengthError(retryError)) {
5030
- throw retryError;
5031
- }
5032
- lastError = retryError;
5033
- }
5034
- }
5035
- throw lastError;
5036
- }
5037
- }
5038
- async embedSingle(text) {
5039
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5040
- method: "POST",
5041
- headers: {
5042
- "Content-Type": "application/json"
5043
- },
5044
- body: JSON.stringify({
5045
- model: this.modelInfo.model,
5046
- prompt: text,
5047
- truncate: false
5048
- })
5049
- });
5050
- if (!response.ok) {
5051
- const error = await response.text();
5052
- throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5053
- }
5054
- const data = await response.json();
5055
- return {
5056
- embedding: data.embedding,
5057
- tokensUsed: this.estimateTokens(text)
5058
- };
5059
- }
5060
- async embedBatch(texts) {
5061
- const results = [];
5062
- for (const text of texts) {
5063
- results.push(await this.embedSingleWithFallback(text));
5064
- }
5065
- return {
5066
- embeddings: results.map((r) => r.embedding),
5067
- totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5068
- };
5069
- }
5070
- getModelInfo() {
5071
- return this.modelInfo;
5072
- }
5073
- };
5074
- var CustomEmbeddingProvider = class {
5075
- constructor(credentials, modelInfo) {
5076
- this.credentials = credentials;
5077
- this.modelInfo = modelInfo;
5078
- }
5079
- splitIntoRequestBatches(texts) {
5080
- const maxBatchSize = this.modelInfo.maxBatchSize;
5081
- if (!maxBatchSize || texts.length <= maxBatchSize) {
5082
- return [texts];
5083
- }
5084
- const batches = [];
5085
- for (let i = 0; i < texts.length; i += maxBatchSize) {
5086
- batches.push(texts.slice(i, i + maxBatchSize));
5087
- }
5088
- return batches;
5089
- }
5090
- async embedRequest(texts) {
5091
- if (texts.length === 0) {
5092
- return {
5093
- embeddings: [],
5094
- totalTokensUsed: 0
5095
- };
5096
- }
5097
- const headers = {
5098
- "Content-Type": "application/json"
5099
- };
5100
- if (this.credentials.apiKey) {
5101
- headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
5102
- }
5103
- const baseUrl = this.credentials.baseUrl ?? "";
5104
- const timeoutMs = this.modelInfo.timeoutMs;
5105
- const controller = new AbortController();
5106
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
5107
- let response;
5108
- try {
5109
- response = await fetch(`${baseUrl}/embeddings`, {
5110
- method: "POST",
5111
- headers,
5112
- body: JSON.stringify({
5113
- model: this.modelInfo.model,
5114
- input: texts
5115
- }),
5116
- signal: controller.signal
5117
- });
5118
- } catch (error) {
5119
- if (error instanceof Error && error.name === "AbortError") {
5120
- throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
5121
- }
5122
- throw error;
5123
- } finally {
5124
- clearTimeout(timeout);
5125
- }
5126
- if (!response.ok) {
5127
- const errorText = await response.text();
5128
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
5129
- throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
5130
- }
5131
- throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
5132
- }
5133
- const data = await response.json();
5134
- if (data.data && Array.isArray(data.data)) {
5135
- if (data.data.length > 0) {
5136
- const actualDims = data.data[0].embedding.length;
5137
- if (actualDims !== this.modelInfo.dimensions) {
5138
- throw new Error(
5139
- `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.`
5140
- );
5213
+ throw retryError;
5214
+ }
5215
+ lastError = retryError;
5141
5216
  }
5142
5217
  }
5143
- if (data.data.length !== texts.length) {
5144
- throw new Error(
5145
- `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
5146
- );
5147
- }
5148
- return {
5149
- embeddings: data.data.map((d) => d.embedding),
5150
- // Rough estimate: ~4 chars per token. Used as fallback when the server
5151
- // doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
5152
- totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
5153
- };
5218
+ throw lastError;
5154
5219
  }
5155
- throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
5156
5220
  }
5157
- async embedQuery(query) {
5158
- const result = await this.embedBatch([query]);
5221
+ async embedSingle(text) {
5222
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5223
+ method: "POST",
5224
+ headers: {
5225
+ "Content-Type": "application/json"
5226
+ },
5227
+ body: JSON.stringify({
5228
+ model: this.modelInfo.model,
5229
+ prompt: text,
5230
+ truncate: false
5231
+ })
5232
+ });
5233
+ if (!response.ok) {
5234
+ const error = (await response.text()).slice(0, 500);
5235
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5236
+ }
5237
+ const data = await response.json();
5159
5238
  return {
5160
- embedding: result.embeddings[0],
5161
- tokensUsed: result.totalTokensUsed
5239
+ embedding: data.embedding,
5240
+ tokensUsed: this.estimateTokens(text)
5162
5241
  };
5163
5242
  }
5164
- async embedDocument(document) {
5165
- const result = await this.embedBatch([document]);
5243
+ async embedBatch(texts) {
5244
+ const results = [];
5245
+ for (const text of texts) {
5246
+ results.push(await this.embedSingleWithFallback(text));
5247
+ }
5166
5248
  return {
5167
- embedding: result.embeddings[0],
5168
- tokensUsed: result.totalTokensUsed
5249
+ embeddings: results.map((r) => r.embedding),
5250
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5169
5251
  };
5170
5252
  }
5253
+ };
5254
+
5255
+ // src/embeddings/providers/openai.ts
5256
+ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
5257
+ constructor(credentials, modelInfo) {
5258
+ super(credentials, modelInfo);
5259
+ }
5171
5260
  async embedBatch(texts) {
5172
- const requestBatches = this.splitIntoRequestBatches(texts);
5173
- const embeddings = [];
5174
- let totalTokensUsed = 0;
5175
- for (const batch of requestBatches) {
5176
- const result = await this.embedRequest(batch);
5177
- embeddings.push(...result.embeddings);
5178
- totalTokensUsed += result.totalTokensUsed;
5261
+ const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5262
+ method: "POST",
5263
+ headers: {
5264
+ Authorization: `Bearer ${this.credentials.apiKey}`,
5265
+ "Content-Type": "application/json"
5266
+ },
5267
+ body: JSON.stringify({
5268
+ model: this.modelInfo.model,
5269
+ input: texts
5270
+ })
5271
+ });
5272
+ if (!response.ok) {
5273
+ const error = (await response.text()).slice(0, 500);
5274
+ throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5179
5275
  }
5276
+ const data = await response.json();
5180
5277
  return {
5181
- embeddings,
5182
- totalTokensUsed
5278
+ embeddings: data.data.map((d) => d.embedding),
5279
+ totalTokensUsed: data.usage.total_tokens
5183
5280
  };
5184
5281
  }
5185
- getModelInfo() {
5186
- return this.modelInfo;
5187
- }
5188
5282
  };
5189
5283
 
5284
+ // src/embeddings/provider.ts
5285
+ function createEmbeddingProvider(configuredProviderInfo) {
5286
+ switch (configuredProviderInfo.provider) {
5287
+ case "github-copilot":
5288
+ return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5289
+ case "openai":
5290
+ return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5291
+ case "google":
5292
+ return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5293
+ case "ollama":
5294
+ return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5295
+ case "custom":
5296
+ return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5297
+ default: {
5298
+ const _exhaustive = configuredProviderInfo;
5299
+ throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
5300
+ }
5301
+ }
5302
+ }
5303
+
5190
5304
  // src/rerank/index.ts
5191
5305
  function createReranker(config) {
5192
5306
  if (!config.enabled) {
@@ -5222,7 +5336,10 @@ var SiliconFlowReranker = class {
5222
5336
  if (this.config.apiKey) {
5223
5337
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
5224
5338
  }
5225
- const baseUrl = this.config.baseUrl ?? "https://api.siliconflow.cn/v1";
5339
+ const baseUrl = this.config.baseUrl;
5340
+ if (!baseUrl) {
5341
+ throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
5342
+ }
5226
5343
  const timeoutMs = this.config.timeoutMs ?? 3e4;
5227
5344
  const controller = new AbortController();
5228
5345
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -5296,20 +5413,21 @@ function createCostEstimate(files, provider) {
5296
5413
  }
5297
5414
  function formatCostEstimate(estimate) {
5298
5415
  const sizeFormatted = formatBytes(estimate.totalSizeBytes);
5416
+ const filesFormatted = `${estimate.filesCount.toLocaleString()} files`;
5299
5417
  const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
5300
5418
  return `
5301
5419
  \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
5302
5420
  \u2502 \u{1F4CA} Indexing Estimate \u2502
5303
5421
  \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
5304
5422
  \u2502 \u2502
5305
- \u2502 Files to index: ${padRight(estimate.filesCount.toLocaleString() + " files", 40)}\u2502
5306
- \u2502 Total size: ${padRight(sizeFormatted, 40)}\u2502
5307
- \u2502 Estimated chunks: ${padRight("~" + estimate.estimatedChunks.toLocaleString() + " chunks", 40)}\u2502
5308
- \u2502 Estimated tokens: ${padRight("~" + estimate.estimatedTokens.toLocaleString() + " tokens", 40)}\u2502
5423
+ \u2502 Files to index: ${filesFormatted.padEnd(40)}\u2502
5424
+ \u2502 Total size: ${sizeFormatted.padEnd(40)}\u2502
5425
+ \u2502 Estimated chunks: ${("~" + estimate.estimatedChunks.toLocaleString() + " chunks").padEnd(40)}\u2502
5426
+ \u2502 Estimated tokens: ${("~" + estimate.estimatedTokens.toLocaleString() + " tokens").padEnd(40)}\u2502
5309
5427
  \u2502 \u2502
5310
- \u2502 Provider: ${padRight(estimate.provider, 52)}\u2502
5311
- \u2502 Model: ${padRight(estimate.model, 52)}\u2502
5312
- \u2502 Cost: ${padRight(costFormatted, 52)}\u2502
5428
+ \u2502 Provider: ${estimate.provider.padEnd(52)}\u2502
5429
+ \u2502 Model: ${estimate.model.padEnd(52)}\u2502
5430
+ \u2502 Cost: ${costFormatted.padEnd(52)}\u2502
5313
5431
  \u2502 \u2502
5314
5432
  \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
5315
5433
  `;
@@ -5321,9 +5439,6 @@ function formatBytes(bytes) {
5321
5439
  const i = Math.floor(Math.log(bytes) / Math.log(k));
5322
5440
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
5323
5441
  }
5324
- function padRight(str, length) {
5325
- return str.padEnd(length);
5326
- }
5327
5442
 
5328
5443
  // src/utils/logger.ts
5329
5444
  var LOG_LEVEL_PRIORITY = {
@@ -5390,6 +5505,10 @@ var Logger = class {
5390
5505
  this.logs.shift();
5391
5506
  }
5392
5507
  }
5508
+ withMetrics(fn) {
5509
+ if (!this.config.metrics) return;
5510
+ fn();
5511
+ }
5393
5512
  search(level, message, data) {
5394
5513
  if (this.config.logSearch) {
5395
5514
  this.log(level, "search", message, data);
@@ -5428,89 +5547,107 @@ var Logger = class {
5428
5547
  this.log("debug", "general", message, data);
5429
5548
  }
5430
5549
  recordIndexingStart() {
5431
- if (!this.config.metrics) return;
5432
- this.metrics.indexingStartTime = Date.now();
5550
+ this.withMetrics(() => {
5551
+ this.metrics.indexingStartTime = Date.now();
5552
+ });
5433
5553
  }
5434
5554
  recordIndexingEnd() {
5435
- if (!this.config.metrics) return;
5436
- this.metrics.indexingEndTime = Date.now();
5555
+ this.withMetrics(() => {
5556
+ this.metrics.indexingEndTime = Date.now();
5557
+ });
5437
5558
  }
5438
5559
  recordFilesScanned(count) {
5439
- if (!this.config.metrics) return;
5440
- this.metrics.filesScanned = count;
5560
+ this.withMetrics(() => {
5561
+ this.metrics.filesScanned = count;
5562
+ });
5441
5563
  }
5442
5564
  recordFilesParsed(count) {
5443
- if (!this.config.metrics) return;
5444
- this.metrics.filesParsed = count;
5565
+ this.withMetrics(() => {
5566
+ this.metrics.filesParsed = count;
5567
+ });
5445
5568
  }
5446
5569
  recordParseDuration(durationMs) {
5447
- if (!this.config.metrics) return;
5448
- this.metrics.parseMs = durationMs;
5570
+ this.withMetrics(() => {
5571
+ this.metrics.parseMs = durationMs;
5572
+ });
5449
5573
  }
5450
5574
  recordChunksProcessed(count) {
5451
- if (!this.config.metrics) return;
5452
- this.metrics.chunksProcessed += count;
5575
+ this.withMetrics(() => {
5576
+ this.metrics.chunksProcessed += count;
5577
+ });
5453
5578
  }
5454
5579
  recordChunksEmbedded(count) {
5455
- if (!this.config.metrics) return;
5456
- this.metrics.chunksEmbedded += count;
5580
+ this.withMetrics(() => {
5581
+ this.metrics.chunksEmbedded += count;
5582
+ });
5457
5583
  }
5458
5584
  recordChunksFromCache(count) {
5459
- if (!this.config.metrics) return;
5460
- this.metrics.chunksFromCache += count;
5585
+ this.withMetrics(() => {
5586
+ this.metrics.chunksFromCache += count;
5587
+ });
5461
5588
  }
5462
5589
  recordChunksRemoved(count) {
5463
- if (!this.config.metrics) return;
5464
- this.metrics.chunksRemoved += count;
5590
+ this.withMetrics(() => {
5591
+ this.metrics.chunksRemoved += count;
5592
+ });
5465
5593
  }
5466
5594
  recordEmbeddingApiCall(tokens) {
5467
- if (!this.config.metrics) return;
5468
- this.metrics.embeddingApiCalls++;
5469
- this.metrics.embeddingTokensUsed += tokens;
5595
+ this.withMetrics(() => {
5596
+ this.metrics.embeddingApiCalls++;
5597
+ this.metrics.embeddingTokensUsed += tokens;
5598
+ });
5470
5599
  }
5471
5600
  recordEmbeddingError() {
5472
- if (!this.config.metrics) return;
5473
- this.metrics.embeddingErrors++;
5601
+ this.withMetrics(() => {
5602
+ this.metrics.embeddingErrors++;
5603
+ });
5474
5604
  }
5475
5605
  recordSearch(durationMs, breakdown) {
5476
- if (!this.config.metrics) return;
5477
- this.metrics.searchCount++;
5478
- this.metrics.searchTotalMs += durationMs;
5479
- this.metrics.searchLastMs = durationMs;
5480
- this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5481
- if (breakdown) {
5482
- this.metrics.embeddingCallMs = breakdown.embeddingMs;
5483
- this.metrics.vectorSearchMs = breakdown.vectorMs;
5484
- this.metrics.keywordSearchMs = breakdown.keywordMs;
5485
- this.metrics.fusionMs = breakdown.fusionMs;
5486
- }
5606
+ this.withMetrics(() => {
5607
+ this.metrics.searchCount++;
5608
+ this.metrics.searchTotalMs += durationMs;
5609
+ this.metrics.searchLastMs = durationMs;
5610
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5611
+ if (breakdown) {
5612
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
5613
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
5614
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
5615
+ this.metrics.fusionMs = breakdown.fusionMs;
5616
+ }
5617
+ });
5487
5618
  }
5488
5619
  recordCacheHit() {
5489
- if (!this.config.metrics) return;
5490
- this.metrics.cacheHits++;
5620
+ this.withMetrics(() => {
5621
+ this.metrics.cacheHits++;
5622
+ });
5491
5623
  }
5492
5624
  recordCacheMiss() {
5493
- if (!this.config.metrics) return;
5494
- this.metrics.cacheMisses++;
5625
+ this.withMetrics(() => {
5626
+ this.metrics.cacheMisses++;
5627
+ });
5495
5628
  }
5496
5629
  recordQueryCacheHit() {
5497
- if (!this.config.metrics) return;
5498
- this.metrics.queryCacheHits++;
5630
+ this.withMetrics(() => {
5631
+ this.metrics.queryCacheHits++;
5632
+ });
5499
5633
  }
5500
5634
  recordQueryCacheSimilarHit() {
5501
- if (!this.config.metrics) return;
5502
- this.metrics.queryCacheSimilarHits++;
5635
+ this.withMetrics(() => {
5636
+ this.metrics.queryCacheSimilarHits++;
5637
+ });
5503
5638
  }
5504
5639
  recordQueryCacheMiss() {
5505
- if (!this.config.metrics) return;
5506
- this.metrics.queryCacheMisses++;
5640
+ this.withMetrics(() => {
5641
+ this.metrics.queryCacheMisses++;
5642
+ });
5507
5643
  }
5508
5644
  recordGc(orphans, chunks, embeddings) {
5509
- if (!this.config.metrics) return;
5510
- this.metrics.gcRuns++;
5511
- this.metrics.gcOrphansRemoved += orphans;
5512
- this.metrics.gcChunksRemoved += chunks;
5513
- this.metrics.gcEmbeddingsRemoved += embeddings;
5645
+ this.withMetrics(() => {
5646
+ this.metrics.gcRuns++;
5647
+ this.metrics.gcOrphansRemoved += orphans;
5648
+ this.metrics.gcChunksRemoved += chunks;
5649
+ this.metrics.gcEmbeddingsRemoved += embeddings;
5650
+ });
5514
5651
  }
5515
5652
  getMetrics() {
5516
5653
  return { ...this.metrics };
@@ -5617,7 +5754,7 @@ function initializeLogger(config) {
5617
5754
  }
5618
5755
 
5619
5756
  // src/native/index.ts
5620
- import * as path7 from "path";
5757
+ import * as path11 from "path";
5621
5758
  import * as os4 from "os";
5622
5759
  import * as module from "module";
5623
5760
  import { fileURLToPath } from "url";
@@ -5641,19 +5778,19 @@ function getNativeBinding() {
5641
5778
  let currentDir;
5642
5779
  let requireTarget;
5643
5780
  if (typeof import.meta !== "undefined" && import.meta.url) {
5644
- currentDir = path7.dirname(fileURLToPath(import.meta.url));
5781
+ currentDir = path11.dirname(fileURLToPath(import.meta.url));
5645
5782
  requireTarget = import.meta.url;
5646
5783
  } else if (typeof __dirname !== "undefined") {
5647
5784
  currentDir = __dirname;
5648
5785
  requireTarget = __filename;
5649
5786
  } else {
5650
5787
  currentDir = process.cwd();
5651
- requireTarget = path7.join(currentDir, "index.js");
5788
+ requireTarget = path11.join(currentDir, "index.js");
5652
5789
  }
5653
5790
  const normalizedDir = currentDir.replace(/\\/g, "/");
5654
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
5655
- const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
5656
- const nativePath = path7.join(packageRoot, "native", bindingName);
5791
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path11.join("src", "native"));
5792
+ const packageRoot = isDevMode ? path11.resolve(currentDir, "../..") : path11.resolve(currentDir, "..");
5793
+ const nativePath = path11.join(packageRoot, "native", bindingName);
5657
5794
  const require2 = module.createRequire(requireTarget);
5658
5795
  return require2(nativePath);
5659
5796
  }
@@ -6223,7 +6360,6 @@ var Database = class {
6223
6360
  this.throwIfClosed();
6224
6361
  return this.inner.getStats();
6225
6362
  }
6226
- // ── Symbol methods ──────────────────────────────────────────────
6227
6363
  upsertSymbol(symbol) {
6228
6364
  this.throwIfClosed();
6229
6365
  this.inner.upsertSymbol(symbol);
@@ -6253,7 +6389,6 @@ var Database = class {
6253
6389
  this.throwIfClosed();
6254
6390
  return this.inner.deleteSymbolsByFile(filePath);
6255
6391
  }
6256
- // ── Call Edge methods ────────────────────────────────────────────
6257
6392
  upsertCallEdge(edge) {
6258
6393
  this.throwIfClosed();
6259
6394
  this.inner.upsertCallEdge(edge);
@@ -6283,7 +6418,6 @@ var Database = class {
6283
6418
  this.throwIfClosed();
6284
6419
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6285
6420
  }
6286
- // ── Branch Symbol methods ────────────────────────────────────────
6287
6421
  addSymbolsToBranch(branch, symbolIds) {
6288
6422
  this.throwIfClosed();
6289
6423
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6316,7 +6450,6 @@ var Database = class {
6316
6450
  if (symbolIds.length === 0) return 0;
6317
6451
  return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6318
6452
  }
6319
- // ── GC methods for symbols/edges ─────────────────────────────────
6320
6453
  gcOrphanSymbols() {
6321
6454
  this.throwIfClosed();
6322
6455
  return this.inner.gcOrphanSymbols();
@@ -6328,7 +6461,7 @@ var Database = class {
6328
6461
  };
6329
6462
 
6330
6463
  // src/indexer/index.ts
6331
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6464
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6332
6465
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
6333
6466
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6334
6467
  "function_declaration",
@@ -6355,7 +6488,15 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6355
6488
  "trigger_declaration",
6356
6489
  "test_declaration",
6357
6490
  "struct_declaration",
6358
- "union_declaration"
6491
+ "union_declaration",
6492
+ // GDScript declarations whose names participate in the call graph.
6493
+ // `function_definition` and `class_definition` are already in the set
6494
+ // above (shared with Python/C/Bash and Python, respectively).
6495
+ "constructor_definition",
6496
+ "enum_definition",
6497
+ "signal_statement",
6498
+ "const_statement",
6499
+ "class_name_statement"
6359
6500
  ]);
6360
6501
  function float32ArrayToBuffer(arr) {
6361
6502
  const float32 = new Float32Array(arr);
@@ -6557,9 +6698,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6557
6698
  return true;
6558
6699
  }
6559
6700
  function isPathWithinRoot(filePath, rootPath) {
6560
- const normalizedFilePath = path8.resolve(filePath);
6561
- const normalizedRoot = path8.resolve(rootPath);
6562
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path8.sep}`);
6701
+ const normalizedFilePath = path12.resolve(filePath);
6702
+ const normalizedRoot = path12.resolve(rootPath);
6703
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6563
6704
  }
6564
6705
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6565
6706
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7612,22 +7753,28 @@ var Indexer = class {
7612
7753
  this.projectRoot = projectRoot;
7613
7754
  this.config = config;
7614
7755
  this.indexPath = this.getIndexPath();
7615
- this.fileHashCachePath = path8.join(this.indexPath, "file-hashes.json");
7616
- this.failedBatchesPath = path8.join(this.indexPath, "failed-batches.json");
7617
- this.indexingLockPath = path8.join(this.indexPath, "indexing.lock");
7756
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7757
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7758
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7618
7759
  this.logger = initializeLogger(config.debug);
7619
7760
  }
7620
7761
  getIndexPath() {
7621
7762
  return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7622
7763
  }
7623
7764
  loadFileHashCache() {
7765
+ if (!existsSync7(this.fileHashCachePath)) {
7766
+ return;
7767
+ }
7624
7768
  try {
7625
- if (existsSync6(this.fileHashCachePath)) {
7626
- const data = readFileSync5(this.fileHashCachePath, "utf-8");
7627
- const parsed = JSON.parse(data);
7628
- this.fileHashCache = new Map(Object.entries(parsed));
7629
- }
7630
- } catch {
7769
+ const data = readFileSync6(this.fileHashCachePath, "utf-8");
7770
+ const parsed = JSON.parse(data);
7771
+ this.fileHashCache = new Map(Object.entries(parsed));
7772
+ } catch (error) {
7773
+ const message = error instanceof Error ? error.message : String(error);
7774
+ this.logger.warn("Failed to load file hash cache, resetting cache state", {
7775
+ fileHashCachePath: this.fileHashCachePath,
7776
+ error: message
7777
+ });
7631
7778
  this.fileHashCache = /* @__PURE__ */ new Map();
7632
7779
  }
7633
7780
  }
@@ -7640,14 +7787,14 @@ var Indexer = class {
7640
7787
  }
7641
7788
  atomicWriteSync(targetPath, data) {
7642
7789
  const tempPath = `${targetPath}.tmp`;
7643
- mkdirSync2(path8.dirname(targetPath), { recursive: true });
7790
+ mkdirSync2(path12.dirname(targetPath), { recursive: true });
7644
7791
  writeFileSync2(tempPath, data);
7645
7792
  renameSync(tempPath, targetPath);
7646
7793
  }
7647
7794
  getScopedRoots() {
7648
- const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7795
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7649
7796
  for (const kbRoot of this.config.knowledgeBases) {
7650
- roots.add(path8.resolve(this.projectRoot, kbRoot));
7797
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
7651
7798
  }
7652
7799
  return Array.from(roots);
7653
7800
  }
@@ -7656,22 +7803,22 @@ var Indexer = class {
7656
7803
  if (this.config.scope !== "global") {
7657
7804
  return branchName;
7658
7805
  }
7659
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7806
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7660
7807
  return `${projectHash}:${branchName}`;
7661
7808
  }
7662
7809
  getLegacyBranchCatalogKey() {
7663
7810
  return this.currentBranch || "default";
7664
7811
  }
7665
7812
  getLegacyMigrationMetadataKey() {
7666
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7813
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7667
7814
  return `index.globalBranchMigration.${projectHash}`;
7668
7815
  }
7669
7816
  getProjectEmbeddingStrategyMetadataKey() {
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 `index.embeddingStrategyVersion.${projectHash}`;
7672
7819
  }
7673
7820
  getProjectForceReembedMetadataKey() {
7674
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7821
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7675
7822
  return `index.forceReembed.${projectHash}`;
7676
7823
  }
7677
7824
  hasProjectForceReembedPending() {
@@ -7765,7 +7912,7 @@ var Indexer = class {
7765
7912
  if (!this.database) {
7766
7913
  return { chunkIds, symbolIds };
7767
7914
  }
7768
- const projectRootPath = path8.resolve(this.projectRoot);
7915
+ const projectRootPath = path12.resolve(this.projectRoot);
7769
7916
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7770
7917
  ...Array.from(this.fileHashCache.keys()).filter(
7771
7918
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7788,7 +7935,7 @@ var Indexer = class {
7788
7935
  if (this.config.scope !== "global") {
7789
7936
  return this.getBranchCatalogCleanupKeys();
7790
7937
  }
7791
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7938
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7792
7939
  const keys = /* @__PURE__ */ new Set();
7793
7940
  const projectChunkIdSet = new Set(projectChunkIds);
7794
7941
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7872,7 +8019,7 @@ var Indexer = class {
7872
8019
  if (!this.database || this.config.scope !== "global") {
7873
8020
  return false;
7874
8021
  }
7875
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
8022
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7876
8023
  const roots = this.getScopedRoots();
7877
8024
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7878
8025
  return this.database.getAllBranches().some(
@@ -7906,7 +8053,7 @@ var Indexer = class {
7906
8053
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7907
8054
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7908
8055
  ]);
7909
- const projectRootPath = path8.resolve(this.projectRoot);
8056
+ const projectRootPath = path12.resolve(this.projectRoot);
7910
8057
  const projectLocalFilePaths = new Set(
7911
8058
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7912
8059
  );
@@ -7975,7 +8122,7 @@ var Indexer = class {
7975
8122
  };
7976
8123
  }
7977
8124
  checkForInterruptedIndexing() {
7978
- return existsSync6(this.indexingLockPath);
8125
+ return existsSync7(this.indexingLockPath);
7979
8126
  }
7980
8127
  acquireIndexingLock() {
7981
8128
  const lockData = {
@@ -7985,13 +8132,13 @@ var Indexer = class {
7985
8132
  writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
7986
8133
  }
7987
8134
  releaseIndexingLock() {
7988
- if (existsSync6(this.indexingLockPath)) {
8135
+ if (existsSync7(this.indexingLockPath)) {
7989
8136
  unlinkSync(this.indexingLockPath);
7990
8137
  }
7991
8138
  }
7992
8139
  async recoverFromInterruptedIndexing() {
7993
8140
  this.logger.warn("Detected interrupted indexing session, recovering...");
7994
- if (existsSync6(this.fileHashCachePath)) {
8141
+ if (existsSync7(this.fileHashCachePath)) {
7995
8142
  unlinkSync(this.fileHashCachePath);
7996
8143
  }
7997
8144
  await this.healthCheck();
@@ -8001,15 +8148,20 @@ var Indexer = class {
8001
8148
  loadFailedBatches(maxChunkTokens) {
8002
8149
  try {
8003
8150
  return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8004
- } catch {
8151
+ } catch (error) {
8152
+ const message = error instanceof Error ? error.message : String(error);
8153
+ this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
8154
+ failedBatchesPath: this.failedBatchesPath,
8155
+ error: message
8156
+ });
8005
8157
  return [];
8006
8158
  }
8007
8159
  }
8008
8160
  loadSerializedFailedBatches() {
8009
- if (!existsSync6(this.failedBatchesPath)) {
8161
+ if (!existsSync7(this.failedBatchesPath)) {
8010
8162
  return [];
8011
8163
  }
8012
- const data = readFileSync5(this.failedBatchesPath, "utf-8");
8164
+ const data = readFileSync6(this.failedBatchesPath, "utf-8");
8013
8165
  const parsed = JSON.parse(data);
8014
8166
  return parsed.map((batch) => {
8015
8167
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -8026,7 +8178,7 @@ var Indexer = class {
8026
8178
  }
8027
8179
  saveFailedBatches(batches) {
8028
8180
  if (batches.length === 0) {
8029
- if (existsSync6(this.failedBatchesPath)) {
8181
+ if (existsSync7(this.failedBatchesPath)) {
8030
8182
  try {
8031
8183
  unlinkSync(this.failedBatchesPath);
8032
8184
  } catch {
@@ -8261,24 +8413,24 @@ var Indexer = class {
8261
8413
  }
8262
8414
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
8263
8415
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8264
- const storePath = path8.join(this.indexPath, "vectors");
8416
+ const storePath = path12.join(this.indexPath, "vectors");
8265
8417
  this.store = new VectorStore(storePath, dimensions);
8266
- const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8267
- if (existsSync6(indexFilePath)) {
8418
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8419
+ if (existsSync7(indexFilePath)) {
8268
8420
  this.store.load();
8269
8421
  }
8270
- const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
8422
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8271
8423
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8272
8424
  try {
8273
8425
  this.invertedIndex.load();
8274
8426
  } catch {
8275
- if (existsSync6(invertedIndexPath)) {
8427
+ if (existsSync7(invertedIndexPath)) {
8276
8428
  await fsPromises2.unlink(invertedIndexPath);
8277
8429
  }
8278
8430
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8279
8431
  }
8280
- const dbPath = path8.join(this.indexPath, "codebase.db");
8281
- let dbIsNew = !existsSync6(dbPath);
8432
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8433
+ let dbIsNew = !existsSync7(dbPath);
8282
8434
  try {
8283
8435
  this.database = new Database(dbPath);
8284
8436
  } catch (error) {
@@ -8358,7 +8510,7 @@ var Indexer = class {
8358
8510
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8359
8511
  return {
8360
8512
  resetCorruptedIndex: true,
8361
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8513
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8362
8514
  };
8363
8515
  }
8364
8516
  throw error;
@@ -8373,7 +8525,7 @@ var Indexer = class {
8373
8525
  return;
8374
8526
  }
8375
8527
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8376
- const storeBasePath = path8.join(this.indexPath, "vectors");
8528
+ const storeBasePath = path12.join(this.indexPath, "vectors");
8377
8529
  const storeIndexPath = `${storeBasePath}.usearch`;
8378
8530
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8379
8531
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8382,18 +8534,18 @@ var Indexer = class {
8382
8534
  let backedUpMetadata = false;
8383
8535
  let rebuiltCount = 0;
8384
8536
  let skippedCount = 0;
8385
- if (existsSync6(backupIndexPath)) {
8537
+ if (existsSync7(backupIndexPath)) {
8386
8538
  unlinkSync(backupIndexPath);
8387
8539
  }
8388
- if (existsSync6(backupMetadataPath)) {
8540
+ if (existsSync7(backupMetadataPath)) {
8389
8541
  unlinkSync(backupMetadataPath);
8390
8542
  }
8391
8543
  try {
8392
- if (existsSync6(storeIndexPath)) {
8544
+ if (existsSync7(storeIndexPath)) {
8393
8545
  renameSync(storeIndexPath, backupIndexPath);
8394
8546
  backedUpIndex = true;
8395
8547
  }
8396
- if (existsSync6(storeMetadataPath)) {
8548
+ if (existsSync7(storeMetadataPath)) {
8397
8549
  renameSync(storeMetadataPath, backupMetadataPath);
8398
8550
  backedUpMetadata = true;
8399
8551
  }
@@ -8414,10 +8566,10 @@ var Indexer = class {
8414
8566
  rebuiltCount += 1;
8415
8567
  }
8416
8568
  store.save();
8417
- if (backedUpIndex && existsSync6(backupIndexPath)) {
8569
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
8418
8570
  unlinkSync(backupIndexPath);
8419
8571
  }
8420
- if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8572
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
8421
8573
  unlinkSync(backupMetadataPath);
8422
8574
  }
8423
8575
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -8430,16 +8582,16 @@ var Indexer = class {
8430
8582
  store.clear();
8431
8583
  } catch {
8432
8584
  }
8433
- if (existsSync6(storeIndexPath)) {
8585
+ if (existsSync7(storeIndexPath)) {
8434
8586
  unlinkSync(storeIndexPath);
8435
8587
  }
8436
- if (existsSync6(storeMetadataPath)) {
8588
+ if (existsSync7(storeMetadataPath)) {
8437
8589
  unlinkSync(storeMetadataPath);
8438
8590
  }
8439
- if (backedUpIndex && existsSync6(backupIndexPath)) {
8591
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
8440
8592
  renameSync(backupIndexPath, storeIndexPath);
8441
8593
  }
8442
- if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8594
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
8443
8595
  renameSync(backupMetadataPath, storeMetadataPath);
8444
8596
  }
8445
8597
  if (backedUpIndex || backedUpMetadata) {
@@ -8458,7 +8610,7 @@ var Indexer = class {
8458
8610
  if (!isSqliteCorruptionError(error)) {
8459
8611
  return false;
8460
8612
  }
8461
- const dbPath = path8.join(this.indexPath, "codebase.db");
8613
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8462
8614
  const warning = this.getCorruptedIndexWarning(dbPath);
8463
8615
  const errorMessage = getErrorMessage(error);
8464
8616
  if (this.config.scope === "global") {
@@ -8481,15 +8633,15 @@ var Indexer = class {
8481
8633
  this.indexCompatibility = null;
8482
8634
  this.fileHashCache.clear();
8483
8635
  const resetPaths = [
8484
- path8.join(this.indexPath, "codebase.db"),
8485
- path8.join(this.indexPath, "codebase.db-shm"),
8486
- path8.join(this.indexPath, "codebase.db-wal"),
8487
- path8.join(this.indexPath, "vectors.usearch"),
8488
- path8.join(this.indexPath, "inverted-index.json"),
8489
- path8.join(this.indexPath, "file-hashes.json"),
8490
- path8.join(this.indexPath, "failed-batches.json"),
8491
- path8.join(this.indexPath, "indexing.lock"),
8492
- path8.join(this.indexPath, "vectors")
8636
+ path12.join(this.indexPath, "codebase.db"),
8637
+ path12.join(this.indexPath, "codebase.db-shm"),
8638
+ path12.join(this.indexPath, "codebase.db-wal"),
8639
+ path12.join(this.indexPath, "vectors.usearch"),
8640
+ path12.join(this.indexPath, "inverted-index.json"),
8641
+ path12.join(this.indexPath, "file-hashes.json"),
8642
+ path12.join(this.indexPath, "failed-batches.json"),
8643
+ path12.join(this.indexPath, "indexing.lock"),
8644
+ path12.join(this.indexPath, "vectors")
8493
8645
  ];
8494
8646
  await Promise.all(resetPaths.map(async (targetPath) => {
8495
8647
  try {
@@ -8754,7 +8906,7 @@ var Indexer = class {
8754
8906
  for (const parsed of parsedFiles) {
8755
8907
  currentFilePaths.add(parsed.path);
8756
8908
  if (parsed.chunks.length === 0) {
8757
- const relativePath = path8.relative(this.projectRoot, parsed.path);
8909
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
8758
8910
  stats.parseFailures.push(relativePath);
8759
8911
  }
8760
8912
  let fileChunkCount = 0;
@@ -9037,7 +9189,7 @@ var Indexer = class {
9037
9189
  for (const requestBatch of requestBatches) {
9038
9190
  queue.add(async () => {
9039
9191
  if (rateLimitBackoffMs > 0) {
9040
- await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
9192
+ await new Promise((resolve12) => setTimeout(resolve12, rateLimitBackoffMs));
9041
9193
  }
9042
9194
  try {
9043
9195
  const result = await pRetry(
@@ -9598,8 +9750,8 @@ var Indexer = class {
9598
9750
  this.indexCompatibility = compatibility;
9599
9751
  return;
9600
9752
  }
9601
- const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9602
- if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9753
+ const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9754
+ if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9603
9755
  throw new Error(
9604
9756
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9605
9757
  );
@@ -9638,7 +9790,7 @@ var Indexer = class {
9638
9790
  const removedChunkKeys = [];
9639
9791
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9640
9792
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9641
- if (!existsSync6(filePath)) {
9793
+ if (!existsSync7(filePath)) {
9642
9794
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9643
9795
  for (const key of chunkKeys) {
9644
9796
  removedChunkKeys.push(key);
@@ -9687,7 +9839,7 @@ var Indexer = class {
9687
9839
  gcOrphanSymbols: 0,
9688
9840
  gcOrphanCallEdges: 0,
9689
9841
  resetCorruptedIndex: true,
9690
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9842
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9691
9843
  };
9692
9844
  }
9693
9845
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10239,12 +10391,15 @@ function formatLogs(logs) {
10239
10391
  return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10240
10392
  }).join("\n");
10241
10393
  }
10394
+ function formatResultHeader(result, index) {
10395
+ 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}`;
10396
+ }
10242
10397
  function formatDefinitionLookup(results, query) {
10243
10398
  if (results.length === 0) {
10244
10399
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
10245
10400
  }
10246
10401
  const formatted = results.map((r, idx) => {
10247
- 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}`;
10402
+ const header = formatResultHeader(r, idx);
10248
10403
  return `${header} (score: ${r.score.toFixed(2)})
10249
10404
  \`\`\`
10250
10405
  ${truncateContent(r.content)}
@@ -10254,7 +10409,7 @@ ${truncateContent(r.content)}
10254
10409
  }
10255
10410
  function formatSearchResults(results, scoreFormat = "similarity") {
10256
10411
  const formatted = results.map((r, idx) => {
10257
- 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}`;
10412
+ const header = formatResultHeader(r, idx);
10258
10413
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
10259
10414
  return `${header} ${scoreLabel}
10260
10415
  \`\`\`
@@ -10264,104 +10419,83 @@ ${truncateContent(r.content)}
10264
10419
  return formatted.join("\n\n");
10265
10420
  }
10266
10421
 
10267
- // src/tools/index.ts
10268
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
10269
- import * as path9 from "path";
10270
- var z = tool.schema;
10271
- var sharedIndexer = null;
10272
- var sharedProjectRoot = "";
10273
- function initializeTools(projectRoot, config) {
10274
- sharedProjectRoot = projectRoot;
10275
- sharedIndexer = new Indexer(projectRoot, config);
10276
- }
10277
- function getSharedIndexer() {
10278
- return getIndexer();
10279
- }
10280
- function refreshIndexerFromConfig() {
10281
- if (!sharedProjectRoot) {
10282
- throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10283
- }
10284
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10285
- }
10286
- function shouldForceLocalizeProjectIndex() {
10287
- const currentConfig = parseConfig(loadRuntimeConfig());
10288
- if (currentConfig.scope !== "project") {
10289
- return false;
10290
- }
10291
- const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10292
- const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10293
- if (!mainRepoRoot) {
10294
- return false;
10295
- }
10296
- const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10297
- return !existsSync7(localIndexPath) && existsSync7(inheritedIndexPath);
10298
- }
10299
- function getIndexer() {
10300
- if (!sharedIndexer) {
10301
- throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10302
- }
10303
- return sharedIndexer;
10304
- }
10305
- function getConfigPath() {
10306
- return resolveWritableProjectConfigPath(sharedProjectRoot);
10307
- }
10308
- function normalizeConfigPathValue(value, baseDir) {
10422
+ // src/tools/knowledge-base-paths.ts
10423
+ import * as path13 from "path";
10424
+ function resolveConfigPathValue(value, baseDir) {
10309
10425
  const trimmed = value.trim();
10310
10426
  if (!trimmed) {
10311
10427
  return trimmed;
10312
10428
  }
10313
- const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10314
- return path9.normalize(absolutePath);
10429
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10430
+ return path13.normalize(absolutePath);
10315
10431
  }
10316
10432
  function serializeConfigPathValue(value, baseDir) {
10317
10433
  const trimmed = value.trim();
10318
10434
  if (!trimmed) {
10319
10435
  return trimmed;
10320
10436
  }
10321
- const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10322
- if (!path9.isAbsolute(trimmed)) {
10323
- return normalizeRelativePath(path9.normalize(trimmed));
10437
+ if (!path13.isAbsolute(trimmed)) {
10438
+ return normalizePathSeparators(path13.normalize(trimmed));
10324
10439
  }
10325
- const relativePath = path9.relative(baseDir, trimmed);
10326
- if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10327
- return normalizeRelativePath(path9.normalize(relativePath || "."));
10440
+ const relativePath = path13.relative(baseDir, trimmed);
10441
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10442
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
10328
10443
  }
10329
- return path9.normalize(trimmed);
10444
+ return path13.normalize(trimmed);
10445
+ }
10446
+ function resolveKnowledgeBasePath(value, projectRoot) {
10447
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10448
+ }
10449
+ function normalizeKnowledgeBasePath2(value, projectRoot) {
10450
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10451
+ }
10452
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10453
+ const normalizedInput = path13.normalize(inputPath);
10454
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10330
10455
  }
10331
- function normalizeKnowledgeBasePaths(config) {
10456
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10457
+ const normalizedInput = path13.normalize(inputPath);
10458
+ return knowledgeBases.findIndex(
10459
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10460
+ );
10461
+ }
10462
+
10463
+ // src/tools/index.ts
10464
+ import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
10465
+ import * as path15 from "path";
10466
+
10467
+ // src/tools/config-state.ts
10468
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
10469
+ import * as path14 from "path";
10470
+ function normalizeKnowledgeBasePaths(config, projectRoot) {
10332
10471
  const normalized = { ...config };
10333
10472
  if (Array.isArray(normalized.knowledgeBases)) {
10334
- normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10335
- return normalizeConfigPathValue(kb, sharedProjectRoot);
10336
- });
10473
+ normalized.knowledgeBases = normalized.knowledgeBases.map(
10474
+ (kb) => resolveConfigPathValue(kb, projectRoot)
10475
+ );
10337
10476
  }
10338
10477
  return normalized;
10339
10478
  }
10340
- function loadRuntimeConfig() {
10341
- const rawConfig = loadMergedConfig(sharedProjectRoot);
10342
- const config = {};
10343
- if (rawConfig && typeof rawConfig === "object") {
10344
- for (const key of Object.keys(rawConfig)) {
10345
- config[key] = rawConfig[key];
10346
- }
10479
+ function toConfigRecord(rawConfig) {
10480
+ if (!rawConfig || typeof rawConfig !== "object") {
10481
+ return {};
10347
10482
  }
10348
- return normalizeKnowledgeBasePaths(config);
10483
+ return { ...rawConfig };
10349
10484
  }
10350
- function loadEditableConfig() {
10351
- const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10352
- const config = {};
10353
- if (rawConfig && typeof rawConfig === "object") {
10354
- for (const key of Object.keys(rawConfig)) {
10355
- config[key] = rawConfig[key];
10356
- }
10357
- }
10358
- return normalizeKnowledgeBasePaths(config);
10485
+ function getConfigPath(projectRoot) {
10486
+ return resolveWritableProjectConfigPath(projectRoot);
10487
+ }
10488
+ function loadRuntimeConfig(projectRoot) {
10489
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot)), projectRoot);
10359
10490
  }
10360
- function saveConfig(config) {
10361
- const configPath = getConfigPath();
10362
- const configDir = path9.dirname(configPath);
10363
- const configBaseDir = path9.dirname(configDir);
10364
- if (!existsSync7(configDir)) {
10491
+ function loadEditableConfig(projectRoot) {
10492
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot)), projectRoot);
10493
+ }
10494
+ function saveConfig(projectRoot, config) {
10495
+ const configPath = getConfigPath(projectRoot);
10496
+ const configDir = path14.dirname(configPath);
10497
+ const configBaseDir = path14.dirname(configDir);
10498
+ if (!existsSync8(configDir)) {
10365
10499
  mkdirSync3(configDir, { recursive: true });
10366
10500
  }
10367
10501
  const serializableConfig = { ...config };
@@ -10372,6 +10506,53 @@ function saveConfig(config) {
10372
10506
  }
10373
10507
  writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10374
10508
  }
10509
+
10510
+ // src/tools/index.ts
10511
+ import * as os5 from "os";
10512
+ function ensureStringArray(value) {
10513
+ return Array.isArray(value) ? value : [];
10514
+ }
10515
+ var z = tool.schema;
10516
+ var indexerMap = /* @__PURE__ */ new Map();
10517
+ var defaultProjectRoot = "";
10518
+ function initializeTools(projectRoot, config) {
10519
+ defaultProjectRoot = projectRoot;
10520
+ const indexer = new Indexer(projectRoot, config);
10521
+ indexerMap.set(projectRoot, indexer);
10522
+ }
10523
+ function refreshIndexerForDirectory(projectRoot) {
10524
+ if (!projectRoot) {
10525
+ throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10526
+ }
10527
+ const indexer = new Indexer(projectRoot, parseConfig(loadRuntimeConfig(projectRoot)));
10528
+ indexerMap.set(projectRoot, indexer);
10529
+ }
10530
+ function shouldForceLocalizeProjectIndex(projectRoot) {
10531
+ const currentConfig = parseConfig(loadRuntimeConfig(projectRoot));
10532
+ if (currentConfig.scope !== "project") {
10533
+ return false;
10534
+ }
10535
+ const localIndexPath = path15.join(projectRoot, ".opencode", "index");
10536
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
10537
+ if (!mainRepoRoot) {
10538
+ return false;
10539
+ }
10540
+ const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10541
+ return !existsSync9(localIndexPath) && existsSync9(inheritedIndexPath);
10542
+ }
10543
+ function getIndexerForProject(directory) {
10544
+ const projectRoot = directory || defaultProjectRoot;
10545
+ if (!projectRoot) {
10546
+ throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10547
+ }
10548
+ let indexer = indexerMap.get(projectRoot);
10549
+ if (!indexer) {
10550
+ const config = parseConfig(loadRuntimeConfig(projectRoot));
10551
+ indexer = new Indexer(projectRoot, config);
10552
+ indexerMap.set(projectRoot, indexer);
10553
+ }
10554
+ return indexer;
10555
+ }
10375
10556
  var codebase_peek = tool({
10376
10557
  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.",
10377
10558
  args: {
@@ -10381,8 +10562,8 @@ var codebase_peek = tool({
10381
10562
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
10382
10563
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type")
10383
10564
  },
10384
- async execute(args) {
10385
- const indexer = getIndexer();
10565
+ async execute(args, context) {
10566
+ const indexer = getIndexerForProject(context?.worktree);
10386
10567
  const results = await indexer.search(args.query, args.limit ?? 10, {
10387
10568
  fileType: args.fileType,
10388
10569
  directory: args.directory,
@@ -10400,16 +10581,17 @@ var index_codebase = tool({
10400
10581
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
10401
10582
  },
10402
10583
  async execute(args, context) {
10403
- let indexer = getIndexer();
10584
+ const projectRoot = context?.worktree || defaultProjectRoot;
10585
+ let indexer = getIndexerForProject(projectRoot);
10404
10586
  if (args.estimateOnly) {
10405
10587
  const estimate = await indexer.estimateCost();
10406
10588
  return formatCostEstimate(estimate);
10407
10589
  }
10408
10590
  if (args.force) {
10409
- if (shouldForceLocalizeProjectIndex()) {
10410
- materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10411
- refreshIndexerFromConfig();
10412
- indexer = getIndexer();
10591
+ if (shouldForceLocalizeProjectIndex(projectRoot)) {
10592
+ materializeLocalProjectConfig(projectRoot, loadProjectConfigLayer(projectRoot));
10593
+ refreshIndexerForDirectory(projectRoot);
10594
+ indexer = getIndexerForProject(projectRoot);
10413
10595
  }
10414
10596
  await indexer.clearIndex();
10415
10597
  }
@@ -10432,8 +10614,8 @@ var index_codebase = tool({
10432
10614
  var index_status = tool({
10433
10615
  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.",
10434
10616
  args: {},
10435
- async execute() {
10436
- const indexer = getIndexer();
10617
+ async execute(_args, context) {
10618
+ const indexer = getIndexerForProject(context?.worktree);
10437
10619
  const status = await indexer.getStatus();
10438
10620
  return formatStatus(status);
10439
10621
  }
@@ -10441,8 +10623,8 @@ var index_status = tool({
10441
10623
  var index_health_check = tool({
10442
10624
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
10443
10625
  args: {},
10444
- async execute() {
10445
- const indexer = getIndexer();
10626
+ async execute(_args, context) {
10627
+ const indexer = getIndexerForProject(context?.worktree);
10446
10628
  const result = await indexer.healthCheck();
10447
10629
  return formatHealthCheck(result);
10448
10630
  }
@@ -10450,8 +10632,8 @@ var index_health_check = tool({
10450
10632
  var index_metrics = tool({
10451
10633
  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.",
10452
10634
  args: {},
10453
- async execute() {
10454
- const indexer = getIndexer();
10635
+ async execute(_args, context) {
10636
+ const indexer = getIndexerForProject(context?.worktree);
10455
10637
  const logger = indexer.getLogger();
10456
10638
  if (!logger.isEnabled()) {
10457
10639
  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```';
@@ -10469,8 +10651,8 @@ var index_logs = tool({
10469
10651
  category: z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
10470
10652
  level: z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
10471
10653
  },
10472
- async execute(args) {
10473
- const indexer = getIndexer();
10654
+ async execute(args, context) {
10655
+ const indexer = getIndexerForProject(context?.worktree);
10474
10656
  const logger = indexer.getLogger();
10475
10657
  if (!logger.isEnabled()) {
10476
10658
  return 'Debug mode is disabled. Enable it in your config:\n\n```json\n{\n "debug": {\n "enabled": true\n }\n}\n```';
@@ -10496,8 +10678,8 @@ var find_similar = tool({
10496
10678
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10497
10679
  excludeFile: z.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
10498
10680
  },
10499
- async execute(args) {
10500
- const indexer = getIndexer();
10681
+ async execute(args, context) {
10682
+ const indexer = getIndexerForProject(context?.worktree);
10501
10683
  const results = await indexer.findSimilar(args.code, args.limit, {
10502
10684
  fileType: args.fileType,
10503
10685
  directory: args.directory,
@@ -10520,8 +10702,8 @@ var codebase_search = tool({
10520
10702
  chunkType: z.enum(["function", "class", "method", "interface", "type", "enum", "struct", "impl", "trait", "module", "other"]).optional().describe("Filter by code chunk type"),
10521
10703
  contextLines: z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
10522
10704
  },
10523
- async execute(args) {
10524
- const indexer = getIndexer();
10705
+ async execute(args, context) {
10706
+ const indexer = getIndexerForProject(context?.worktree);
10525
10707
  const results = await indexer.search(args.query, args.limit ?? 5, {
10526
10708
  fileType: args.fileType,
10527
10709
  directory: args.directory,
@@ -10542,8 +10724,8 @@ var implementation_lookup = tool({
10542
10724
  fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
10543
10725
  directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
10544
10726
  },
10545
- async execute(args) {
10546
- const indexer = getIndexer();
10727
+ async execute(args, context) {
10728
+ const indexer = getIndexerForProject(context?.worktree);
10547
10729
  const results = await indexer.search(args.query, args.limit ?? 5, {
10548
10730
  fileType: args.fileType,
10549
10731
  directory: args.directory,
@@ -10559,8 +10741,8 @@ var call_graph = tool({
10559
10741
  direction: z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
10560
10742
  symbolId: z.string().optional().describe("Symbol ID (required for 'callees' direction, returned by previous call_graph queries)")
10561
10743
  },
10562
- async execute(args) {
10563
- const indexer = getIndexer();
10744
+ async execute(args, context) {
10745
+ const indexer = getIndexerForProject(context?.worktree);
10564
10746
  if (args.direction === "callees") {
10565
10747
  if (!args.symbolId) {
10566
10748
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
@@ -10589,38 +10771,67 @@ var add_knowledge_base = tool({
10589
10771
  args: {
10590
10772
  path: z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to project root)")
10591
10773
  },
10592
- async execute(args) {
10774
+ async execute(args, context) {
10775
+ const projectRoot = context?.worktree || defaultProjectRoot;
10593
10776
  const inputPath = args.path.trim();
10594
- const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10595
- if (!existsSync7(resolvedPath)) {
10596
- return `Error: Directory does not exist: ${resolvedPath}`;
10777
+ const normalizedPath = path15.resolve(
10778
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, projectRoot)
10779
+ );
10780
+ if (!existsSync9(normalizedPath)) {
10781
+ return `Error: Directory does not exist: ${normalizedPath}`;
10782
+ }
10783
+ let realPath;
10784
+ try {
10785
+ realPath = realpathSync(normalizedPath);
10786
+ } catch {
10787
+ return `Error: Cannot resolve path: ${normalizedPath}`;
10788
+ }
10789
+ const blockedPrefixes = [
10790
+ "/etc",
10791
+ "/proc",
10792
+ "/sys",
10793
+ "/dev",
10794
+ "/boot",
10795
+ "/root",
10796
+ "/var/run",
10797
+ "/var/log"
10798
+ ];
10799
+ const homeDir = os5.homedir();
10800
+ const sensitiveDotDirs = [".ssh", ".gnupg", ".aws", ".config/gcloud", ".docker", ".kube"];
10801
+ for (const prefix of blockedPrefixes) {
10802
+ if (realPath === prefix || realPath.startsWith(prefix + "/")) {
10803
+ return `Error: Adding system directory as knowledge base is not allowed: ${normalizedPath}`;
10804
+ }
10805
+ }
10806
+ for (const dotDir of sensitiveDotDirs) {
10807
+ const sensitiveDir = path15.join(homeDir, dotDir);
10808
+ if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10809
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10810
+ }
10597
10811
  }
10598
10812
  try {
10599
- const stat4 = statSync2(resolvedPath);
10813
+ const stat4 = statSync3(normalizedPath);
10600
10814
  if (!stat4.isDirectory()) {
10601
- return `Error: Path is not a directory: ${resolvedPath}`;
10815
+ return `Error: Path is not a directory: ${normalizedPath}`;
10602
10816
  }
10603
10817
  } catch (error) {
10604
- return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
10818
+ return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10605
10819
  }
10606
- const config = loadEditableConfig();
10607
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10608
- const normalizedPath = path9.normalize(resolvedPath);
10609
- const alreadyExists = knowledgeBases.some(
10610
- (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
10611
- );
10820
+ const config = loadEditableConfig(projectRoot);
10821
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10822
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, projectRoot);
10612
10823
  if (alreadyExists) {
10613
- return `Knowledge base already configured: ${resolvedPath}`;
10824
+ return `Knowledge base already configured: ${normalizedPath}`;
10614
10825
  }
10615
- knowledgeBases.push(resolvedPath);
10826
+ knowledgeBases.push(normalizedPath);
10616
10827
  config.knowledgeBases = knowledgeBases;
10617
- saveConfig(config);
10618
- refreshIndexerFromConfig();
10619
- let result = `${resolvedPath}
10828
+ saveConfig(projectRoot, config);
10829
+ refreshIndexerForDirectory(projectRoot);
10830
+ let result = `${normalizedPath}
10620
10831
  `;
10621
10832
  result += `Total knowledge bases: ${knowledgeBases.length}
10622
10833
  `;
10623
- result += `Config saved to: ${getConfigPath()}
10834
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10624
10835
  `;
10625
10836
  result += `
10626
10837
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10630,9 +10841,10 @@ Run /index to rebuild the index with the new knowledge base.`;
10630
10841
  var list_knowledge_bases = tool({
10631
10842
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10632
10843
  args: {},
10633
- async execute() {
10634
- const config = loadRuntimeConfig();
10635
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10844
+ async execute(_args, context) {
10845
+ const projectRoot = context?.worktree || defaultProjectRoot;
10846
+ const config = loadRuntimeConfig(projectRoot);
10847
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10636
10848
  if (knowledgeBases.length === 0) {
10637
10849
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
10638
10850
  }
@@ -10641,8 +10853,8 @@ var list_knowledge_bases = tool({
10641
10853
  `;
10642
10854
  for (let i = 0; i < knowledgeBases.length; i++) {
10643
10855
  const kb = knowledgeBases[i];
10644
- const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10645
- const exists = existsSync7(resolvedPath);
10856
+ const resolvedPath = resolveKnowledgeBasePath(kb, projectRoot);
10857
+ const exists = existsSync9(resolvedPath);
10646
10858
  result += `[${i + 1}] ${kb}
10647
10859
  `;
10648
10860
  result += ` Resolved: ${resolvedPath}
@@ -10651,7 +10863,7 @@ var list_knowledge_bases = tool({
10651
10863
  `;
10652
10864
  if (exists) {
10653
10865
  try {
10654
- const stat4 = statSync2(resolvedPath);
10866
+ const stat4 = statSync3(resolvedPath);
10655
10867
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
10656
10868
  `;
10657
10869
  } catch {
@@ -10659,7 +10871,7 @@ var list_knowledge_bases = tool({
10659
10871
  }
10660
10872
  result += "\n";
10661
10873
  }
10662
- result += `Config file: ${getConfigPath()}`;
10874
+ result += `Config file: ${getConfigPath(projectRoot)}`;
10663
10875
  return result;
10664
10876
  }
10665
10877
  });
@@ -10668,17 +10880,15 @@ var remove_knowledge_base = tool({
10668
10880
  args: {
10669
10881
  path: z.string().describe("Path of the knowledge base to remove (must match the configured path exactly)")
10670
10882
  },
10671
- async execute(args) {
10883
+ async execute(args, context) {
10884
+ const projectRoot = context?.worktree || defaultProjectRoot;
10672
10885
  const inputPath = args.path.trim();
10673
- const config = loadEditableConfig();
10674
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10886
+ const config = loadEditableConfig(projectRoot);
10887
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10675
10888
  if (knowledgeBases.length === 0) {
10676
10889
  return "No knowledge bases configured.";
10677
10890
  }
10678
- const normalizedInput = path9.normalize(inputPath);
10679
- const index = knowledgeBases.findIndex(
10680
- (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
10681
- );
10891
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot);
10682
10892
  if (index === -1) {
10683
10893
  let result2 = `Knowledge base not found: ${inputPath}
10684
10894
 
@@ -10693,14 +10903,14 @@ var remove_knowledge_base = tool({
10693
10903
  }
10694
10904
  const removed = knowledgeBases.splice(index, 1)[0];
10695
10905
  config.knowledgeBases = knowledgeBases;
10696
- saveConfig(config);
10697
- refreshIndexerFromConfig();
10906
+ saveConfig(projectRoot, config);
10907
+ refreshIndexerForDirectory(projectRoot);
10698
10908
  let result = `Removed: ${removed}
10699
10909
 
10700
10910
  `;
10701
10911
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10702
10912
  `;
10703
- result += `Config saved to: ${getConfigPath()}
10913
+ result += `Config saved to: ${getConfigPath(projectRoot)}
10704
10914
  `;
10705
10915
  result += `
10706
10916
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -10709,8 +10919,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
10709
10919
  });
10710
10920
 
10711
10921
  // src/commands/loader.ts
10712
- import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
10713
- import * as path10 from "path";
10922
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
10923
+ import * as path16 from "path";
10714
10924
  function parseFrontmatter(content) {
10715
10925
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10716
10926
  const match = content.match(frontmatterRegex);
@@ -10731,15 +10941,21 @@ function parseFrontmatter(content) {
10731
10941
  }
10732
10942
  function loadCommandsFromDirectory(commandsDir) {
10733
10943
  const commands = /* @__PURE__ */ new Map();
10734
- if (!existsSync8(commandsDir)) {
10944
+ if (!existsSync10(commandsDir)) {
10735
10945
  return commands;
10736
10946
  }
10737
10947
  const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
10738
10948
  for (const file of files) {
10739
- const filePath = path10.join(commandsDir, file);
10740
- const content = readFileSync6(filePath, "utf-8");
10949
+ const filePath = path16.join(commandsDir, file);
10950
+ let content;
10951
+ try {
10952
+ content = readFileSync7(filePath, "utf-8");
10953
+ } catch (error) {
10954
+ const message = error instanceof Error ? error.message : String(error);
10955
+ throw new Error(`Failed to load command file ${filePath}: ${message}`);
10956
+ }
10741
10957
  const { frontmatter, body } = parseFrontmatter(content);
10742
- const name = path10.basename(file, ".md");
10958
+ const name = path16.basename(file, ".md");
10743
10959
  const description = frontmatter.description || `Run the ${name} command`;
10744
10960
  commands.set(name, {
10745
10961
  description,
@@ -10749,7 +10965,7 @@ function loadCommandsFromDirectory(commandsDir) {
10749
10965
  return commands;
10750
10966
  }
10751
10967
 
10752
- // src/routing-hints.ts
10968
+ // src/routing-hints-patterns.ts
10753
10969
  var EXTERNAL_HINTS = [
10754
10970
  "docs",
10755
10971
  "documentation",
@@ -10840,6 +11056,8 @@ var SNAKE_PATTERN = /\b[a-z0-9]+_[a-z0-9_]+\b/g;
10840
11056
  var KEBAB_PATTERN = /\b[a-z0-9]+-[a-z0-9-]+\b/g;
10841
11057
  var BACKTICK_IDENTIFIER_PATTERN = /`([^`]+)`/g;
10842
11058
  var BACKTICK_IDENTIFIER_PRESENCE_PATTERN = /`([^`]+)`/;
11059
+ var DOUBLE_QUOTED_PATTERN = /"[^"]+"/;
11060
+ var SINGLE_QUOTED_PATTERN = /'[^']+'/;
10843
11061
  function normalizeText(text) {
10844
11062
  return text.trim().replace(/\s+/g, " ");
10845
11063
  }
@@ -10852,6 +11070,21 @@ function countWords(text) {
10852
11070
  }
10853
11071
  return text.split(/\s+/).filter(Boolean).length;
10854
11072
  }
11073
+ function isExternalLookup(text) {
11074
+ return URL_PATTERN.test(text) || includesHint(text, EXTERNAL_HINTS);
11075
+ }
11076
+ function hasConceptualDiscoveryHint(text) {
11077
+ return includesHint(text, CONCEPTUAL_DISCOVERY_HINTS);
11078
+ }
11079
+ function hasDefinitionHint(text) {
11080
+ return includesHint(text, DEFINITION_HINTS);
11081
+ }
11082
+ function hasExactMatchHint(text) {
11083
+ return includesHint(text, EXACT_MATCH_HINTS);
11084
+ }
11085
+ function hasNonDiscoveryHint(text) {
11086
+ return includesHint(text, NON_DISCOVERY_HINTS);
11087
+ }
10855
11088
  function hasIdentifierShape(text) {
10856
11089
  const matches = [
10857
11090
  ...text.match(CAMEL_OR_PASCAL_PATTERN) ?? [],
@@ -10867,11 +11100,13 @@ function hasIdentifierShape(text) {
10867
11100
  });
10868
11101
  }
10869
11102
  function containsQuotedIdentifier(text) {
10870
- return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || /"[^"]+"/.test(text) || /'[^']+'/.test(text);
11103
+ return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || DOUBLE_QUOTED_PATTERN.test(text) || SINGLE_QUOTED_PATTERN.test(text);
10871
11104
  }
10872
11105
  function looksLikeDirectPath(text) {
10873
11106
  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);
10874
11107
  }
11108
+
11109
+ // src/routing-hints.ts
10875
11110
  function extractUserText(parts) {
10876
11111
  return normalizeText(
10877
11112
  parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join(" ")
@@ -10887,21 +11122,21 @@ function assessRoutingIntent(text) {
10887
11122
  reason: "empty_text"
10888
11123
  };
10889
11124
  }
10890
- if (URL_PATTERN.test(lowered) || includesHint(lowered, EXTERNAL_HINTS)) {
11125
+ if (isExternalLookup(lowered)) {
10891
11126
  return {
10892
11127
  intent: "external",
10893
11128
  text: normalizedText,
10894
11129
  reason: "external_lookup"
10895
11130
  };
10896
11131
  }
10897
- const hasConceptualHint = includesHint(lowered, CONCEPTUAL_DISCOVERY_HINTS);
10898
- const hasDefinitionHint = includesHint(lowered, DEFINITION_HINTS);
10899
- const hasExactMatchHint = includesHint(lowered, EXACT_MATCH_HINTS);
10900
- const hasNonDiscoveryHint = includesHint(lowered, NON_DISCOVERY_HINTS);
11132
+ const matchedConceptualHint = hasConceptualDiscoveryHint(lowered);
11133
+ const matchedDefinitionHint = hasDefinitionHint(lowered);
11134
+ const matchedExactMatchHint = hasExactMatchHint(lowered);
11135
+ const matchedNonDiscoveryHint = hasNonDiscoveryHint(lowered);
10901
11136
  const hasIdentifier = hasIdentifierShape(normalizedText);
10902
11137
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
10903
11138
  const shortQuery = countWords(lowered) <= 10;
10904
- if (hasNonDiscoveryHint && !hasConceptualHint) {
11139
+ if (matchedNonDiscoveryHint && !matchedConceptualHint) {
10905
11140
  return {
10906
11141
  intent: "other",
10907
11142
  text: normalizedText,
@@ -10915,21 +11150,21 @@ function assessRoutingIntent(text) {
10915
11150
  reason: "direct_path_reference"
10916
11151
  };
10917
11152
  }
10918
- if ((hasDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
11153
+ if ((matchedDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
10919
11154
  return {
10920
11155
  intent: "definition_lookup",
10921
11156
  text: normalizedText,
10922
11157
  reason: "definition_lookup_request"
10923
11158
  };
10924
11159
  }
10925
- if ((hasExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !hasConceptualHint && shortQuery) {
11160
+ if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
10926
11161
  return {
10927
11162
  intent: "exact_identifier",
10928
11163
  text: normalizedText,
10929
- reason: hasExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
11164
+ reason: matchedExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
10930
11165
  };
10931
11166
  }
10932
- if (hasConceptualHint) {
11167
+ if (matchedConceptualHint) {
10933
11168
  return {
10934
11169
  intent: "local_conceptual",
10935
11170
  text: normalizedText,
@@ -11018,33 +11253,55 @@ var RoutingHintController = class {
11018
11253
  };
11019
11254
 
11020
11255
  // src/index.ts
11021
- var activeWatcher = null;
11022
- function replaceActiveWatcher(nextWatcher) {
11023
- activeWatcher?.stop();
11024
- activeWatcher = nextWatcher;
11256
+ var activeWatchers = /* @__PURE__ */ new Map();
11257
+ function replaceActiveWatcher(projectRoot, nextWatcher) {
11258
+ const existing = activeWatchers.get(projectRoot);
11259
+ if (existing) {
11260
+ existing.stop();
11261
+ activeWatchers.delete(projectRoot);
11262
+ }
11263
+ if (nextWatcher) {
11264
+ activeWatchers.set(projectRoot, nextWatcher);
11265
+ }
11025
11266
  }
11026
11267
  function getCommandsDir() {
11027
11268
  let currentDir = process.cwd();
11028
11269
  if (typeof import.meta !== "undefined" && import.meta.url) {
11029
- currentDir = path11.dirname(fileURLToPath2(import.meta.url));
11270
+ currentDir = path17.dirname(fileURLToPath2(import.meta.url));
11271
+ }
11272
+ return path17.join(currentDir, "..", "commands");
11273
+ }
11274
+ function appendRoutingHints(output, hints, preferredRole) {
11275
+ const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
11276
+ if (Array.isArray(preferredBucket)) {
11277
+ preferredBucket.push(...hints);
11278
+ return;
11279
+ }
11280
+ if (Array.isArray(output.system)) {
11281
+ output.system.push(...hints);
11030
11282
  }
11031
- return path11.join(currentDir, "..", "commands");
11032
11283
  }
11033
- var plugin = async ({ directory }) => {
11284
+ var plugin = async ({ directory, worktree }) => {
11034
11285
  try {
11035
- const projectRoot = directory;
11286
+ const projectRoot = worktree || directory;
11036
11287
  const rawConfig = loadMergedConfig(projectRoot);
11037
11288
  const config = parseConfig(rawConfig);
11038
11289
  initializeTools(projectRoot, config);
11039
- const indexer = getSharedIndexer();
11040
- const routingHints = config.search.routingHints ? new RoutingHintController(() => indexer.getStatus()) : null;
11041
- const isValidProject = !config.indexing.requireProjectMarker || hasProjectMarker(projectRoot);
11042
- if (!isValidProject) {
11290
+ const getProjectIndexer = () => getIndexerForProject(projectRoot);
11291
+ const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus()) : null;
11292
+ const isHomeDir = path17.resolve(projectRoot) === path17.resolve(os6.homedir());
11293
+ const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
11294
+ if (isHomeDir) {
11295
+ console.warn(
11296
+ `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
11297
+ );
11298
+ } else if (!isValidProject) {
11043
11299
  console.warn(
11044
11300
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
11045
11301
  );
11046
11302
  }
11047
11303
  if (config.indexing.autoIndex && isValidProject) {
11304
+ const indexer = getProjectIndexer();
11048
11305
  indexer.initialize().then(() => {
11049
11306
  indexer.index().catch(() => {
11050
11307
  });
@@ -11052,9 +11309,9 @@ var plugin = async ({ directory }) => {
11052
11309
  });
11053
11310
  }
11054
11311
  if (config.indexing.watchFiles && isValidProject) {
11055
- replaceActiveWatcher(createWatcherWithIndexer(getSharedIndexer, projectRoot, config));
11312
+ replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config));
11056
11313
  } else {
11057
- replaceActiveWatcher(null);
11314
+ replaceActiveWatcher(projectRoot, null);
11058
11315
  }
11059
11316
  return {
11060
11317
  tool: {
@@ -11076,8 +11333,18 @@ var plugin = async ({ directory }) => {
11076
11333
  routingHints?.observeUserMessage(input.sessionID, output.parts);
11077
11334
  },
11078
11335
  async "experimental.chat.system.transform"(input, output) {
11336
+ if (config.search.routingHintRole !== "system") {
11337
+ return;
11338
+ }
11339
+ const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11340
+ appendRoutingHints(output, hints, "system");
11341
+ },
11342
+ async "experimental.chat.developer.transform"(input, output) {
11343
+ if (config.search.routingHintRole !== "developer") {
11344
+ return;
11345
+ }
11079
11346
  const hints = await routingHints?.getSystemHints(input.sessionID) ?? [];
11080
- output.system.push(...hints);
11347
+ appendRoutingHints(output, hints, "developer");
11081
11348
  },
11082
11349
  async "tool.execute.after"(input) {
11083
11350
  routingHints?.markToolUsed(input.sessionID, input.tool);
@@ -11091,8 +11358,8 @@ var plugin = async ({ directory }) => {
11091
11358
  }
11092
11359
  }
11093
11360
  };
11094
- } catch (error) {
11095
- console.error("[codebase-index] Failed to initialize plugin:", error);
11361
+ } catch {
11362
+ console.error("[codebase-index] Failed to initialize plugin (check config and network)");
11096
11363
  return {
11097
11364
  tool: void 0,
11098
11365
  async config() {