opencode-codebase-index 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,7 @@ var require_eventemitter3 = __commonJS({
647
647
  });
648
648
 
649
649
  // src/index.ts
650
- import * as path11 from "path";
650
+ import * as path17 from "path";
651
651
  import { fileURLToPath as fileURLToPath2 } from "url";
652
652
 
653
653
  // src/config/constants.ts
@@ -664,7 +664,8 @@ var DEFAULT_INCLUDE = [
664
664
  "**/*.{md,mdx}",
665
665
  "**/*.{sh,bash,zsh}",
666
666
  "**/*.{txt,html,htm}",
667
- "**/*.zig"
667
+ "**/*.zig",
668
+ "**/*.gd"
668
669
  ];
669
670
  var DEFAULT_EXCLUDE = [
670
671
  "**/node_modules/**",
@@ -763,28 +764,7 @@ var AUTO_DETECT_PROVIDER_ORDER = [
763
764
  "google"
764
765
  ];
765
766
 
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
767
+ // src/config/defaults.ts
788
768
  function getDefaultIndexingConfig() {
789
769
  return {
790
770
  autoIndex: false,
@@ -816,12 +796,6 @@ function getDefaultSearchConfig() {
816
796
  routingHints: true
817
797
  };
818
798
  }
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
799
  function getDefaultRerankerBaseUrl(provider) {
826
800
  switch (provider) {
827
801
  case "cohere":
@@ -844,8 +818,37 @@ function getDefaultDebugConfig() {
844
818
  metrics: true
845
819
  };
846
820
  }
821
+
822
+ // src/config/env-substitution.ts
823
+ var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
824
+ var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
825
+ function substituteEnvString(value, keyPath) {
826
+ const match = value.match(ENV_REFERENCE_PATTERN);
827
+ if (!match) {
828
+ if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
829
+ throw new Error(
830
+ `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
831
+ );
832
+ }
833
+ return value;
834
+ }
835
+ const variableName = match[1];
836
+ const envValue = process.env[variableName];
837
+ if (envValue === void 0) {
838
+ throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
839
+ }
840
+ return envValue;
841
+ }
842
+
843
+ // src/config/validators.ts
847
844
  var VALID_SCOPES = ["project", "global"];
848
845
  var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
846
+ function isValidFusionStrategy(value) {
847
+ return value === "weighted" || value === "rrf";
848
+ }
849
+ function isValidRerankerProvider(value) {
850
+ return value === "cohere" || value === "jina" || value === "custom";
851
+ }
849
852
  function isValidProvider(value) {
850
853
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
851
854
  }
@@ -873,6 +876,8 @@ function getResolvedStringArray(value, keyPath) {
873
876
  function isValidLogLevel(value) {
874
877
  return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
875
878
  }
879
+
880
+ // src/config/schema.ts
876
881
  function parseConfig(raw) {
877
882
  const input = raw && typeof raw === "object" ? raw : {};
878
883
  const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
@@ -927,9 +932,9 @@ function parseConfig(raw) {
927
932
  const rawAdditionalInclude = input.additionalInclude;
928
933
  const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
929
934
  let embeddingProvider;
930
- let embeddingModel = void 0;
931
- let customProvider = void 0;
932
- let reranker = void 0;
935
+ let embeddingModel;
936
+ let customProvider;
937
+ let reranker;
933
938
  if (embeddingProviderValue === "custom") {
934
939
  embeddingProvider = "custom";
935
940
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -962,7 +967,7 @@ function parseConfig(raw) {
962
967
  embeddingProvider = embeddingProviderValue;
963
968
  const rawEmbeddingModel = input.embeddingModel;
964
969
  if (typeof rawEmbeddingModel === "string") {
965
- const embeddingModelValue = substituteEnvString(rawEmbeddingModel, "$root.embeddingModel");
970
+ const embeddingModelValue = getResolvedString(rawEmbeddingModel, "$root.embeddingModel");
966
971
  if (embeddingModelValue) {
967
972
  embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
968
973
  }
@@ -1025,16 +1030,20 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1025
1030
  );
1026
1031
 
1027
1032
  // src/config/merger.ts
1028
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1033
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
1029
1034
  import * as os2 from "os";
1030
- import * as path3 from "path";
1035
+ import * as path6 from "path";
1031
1036
 
1032
1037
  // src/config/paths.ts
1033
- import { existsSync as existsSync2 } from "fs";
1038
+ import { existsSync as existsSync3 } from "fs";
1034
1039
  import * as os from "os";
1035
- import * as path2 from "path";
1040
+ import * as path3 from "path";
1036
1041
 
1037
1042
  // src/git/index.ts
1043
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1044
+ import * as path2 from "path";
1045
+
1046
+ // src/git/refs.ts
1038
1047
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
1039
1048
  import * as path from "path";
1040
1049
  function readPackedRefs(gitDir) {
@@ -1067,38 +1076,40 @@ function resolveCommonGitDir(gitDir) {
1067
1076
  }
1068
1077
  return gitDir;
1069
1078
  }
1079
+
1080
+ // src/git/index.ts
1070
1081
  function resolveWorktreeMainRepoRoot(repoRoot) {
1071
1082
  const gitDir = resolveGitDir(repoRoot);
1072
1083
  if (!gitDir) {
1073
1084
  return null;
1074
1085
  }
1075
1086
  const commonGitDir = resolveCommonGitDir(gitDir);
1076
- if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1087
+ if (commonGitDir === gitDir || path2.basename(commonGitDir) !== ".git") {
1077
1088
  return null;
1078
1089
  }
1079
- const mainRepoRoot = path.dirname(commonGitDir);
1080
- if (!existsSync(mainRepoRoot)) {
1090
+ const mainRepoRoot = path2.dirname(commonGitDir);
1091
+ if (!existsSync2(mainRepoRoot)) {
1081
1092
  return null;
1082
1093
  }
1083
- return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1094
+ return path2.resolve(mainRepoRoot) === path2.resolve(repoRoot) ? null : mainRepoRoot;
1084
1095
  }
1085
1096
  function resolveGitDir(repoRoot) {
1086
- const gitPath = path.join(repoRoot, ".git");
1087
- if (!existsSync(gitPath)) {
1097
+ const gitPath = path2.join(repoRoot, ".git");
1098
+ if (!existsSync2(gitPath)) {
1088
1099
  return null;
1089
1100
  }
1090
1101
  try {
1091
- const stat4 = statSync(gitPath);
1102
+ const stat4 = statSync2(gitPath);
1092
1103
  if (stat4.isDirectory()) {
1093
1104
  return gitPath;
1094
1105
  }
1095
1106
  if (stat4.isFile()) {
1096
- const content = readFileSync(gitPath, "utf-8").trim();
1107
+ const content = readFileSync2(gitPath, "utf-8").trim();
1097
1108
  const match = content.match(/^gitdir:\s*(.+)$/);
1098
1109
  if (match) {
1099
1110
  const gitdir = match[1];
1100
- const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1101
- if (existsSync(resolvedPath)) {
1111
+ const resolvedPath = path2.isAbsolute(gitdir) ? gitdir : path2.resolve(repoRoot, gitdir);
1112
+ if (existsSync2(resolvedPath)) {
1102
1113
  return resolvedPath;
1103
1114
  }
1104
1115
  }
@@ -1115,12 +1126,12 @@ function getCurrentBranch(repoRoot) {
1115
1126
  if (!gitDir) {
1116
1127
  return null;
1117
1128
  }
1118
- const headPath = path.join(gitDir, "HEAD");
1119
- if (!existsSync(headPath)) {
1129
+ const headPath = path2.join(gitDir, "HEAD");
1130
+ if (!existsSync2(headPath)) {
1120
1131
  return null;
1121
1132
  }
1122
1133
  try {
1123
- const headContent = readFileSync(headPath, "utf-8").trim();
1134
+ const headContent = readFileSync2(headPath, "utf-8").trim();
1124
1135
  const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1125
1136
  if (match) {
1126
1137
  return match[1];
@@ -1139,8 +1150,8 @@ function getBaseBranch(repoRoot) {
1139
1150
  const candidates = ["main", "master", "develop", "trunk"];
1140
1151
  if (refStoreDir) {
1141
1152
  for (const candidate of candidates) {
1142
- const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1143
- if (existsSync(refPath)) {
1153
+ const refPath = path2.join(refStoreDir, "refs", "heads", candidate);
1154
+ if (existsSync2(refPath)) {
1144
1155
  return candidate;
1145
1156
  }
1146
1157
  const packedRefs = readPackedRefs(refStoreDir);
@@ -1160,44 +1171,44 @@ function getBranchOrDefault(repoRoot) {
1160
1171
  function getHeadPath(repoRoot) {
1161
1172
  const gitDir = resolveGitDir(repoRoot);
1162
1173
  if (gitDir) {
1163
- return path.join(gitDir, "HEAD");
1174
+ return path2.join(gitDir, "HEAD");
1164
1175
  }
1165
- return path.join(repoRoot, ".git", "HEAD");
1176
+ return path2.join(repoRoot, ".git", "HEAD");
1166
1177
  }
1167
1178
 
1168
1179
  // 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");
1180
+ var PROJECT_CONFIG_RELATIVE_PATH = path3.join(".opencode", "codebase-index.json");
1181
+ var PROJECT_INDEX_RELATIVE_PATH = path3.join(".opencode", "index");
1171
1182
  function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1172
1183
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1173
1184
  if (!mainRepoRoot) {
1174
1185
  return null;
1175
1186
  }
1176
- const fallbackPath = path2.join(mainRepoRoot, relativePath);
1177
- return existsSync2(fallbackPath) ? fallbackPath : null;
1187
+ const fallbackPath = path3.join(mainRepoRoot, relativePath);
1188
+ return existsSync3(fallbackPath) ? fallbackPath : null;
1178
1189
  }
1179
1190
  function hasProjectConfig(projectRoot) {
1180
- return existsSync2(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1191
+ return existsSync3(path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1181
1192
  }
1182
1193
  function getGlobalIndexPath() {
1183
- return path2.join(os.homedir(), ".opencode", "global-index");
1194
+ return path3.join(os.homedir(), ".opencode", "global-index");
1184
1195
  }
1185
1196
  function resolveProjectConfigPath(projectRoot) {
1186
- const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1187
- if (existsSync2(localConfigPath)) {
1197
+ const localConfigPath = path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1198
+ if (existsSync3(localConfigPath)) {
1188
1199
  return localConfigPath;
1189
1200
  }
1190
1201
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1191
1202
  }
1192
1203
  function resolveWritableProjectConfigPath(projectRoot) {
1193
- return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1204
+ return path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1194
1205
  }
1195
1206
  function resolveProjectIndexPath(projectRoot, scope) {
1196
1207
  if (scope === "global") {
1197
1208
  return getGlobalIndexPath();
1198
1209
  }
1199
- const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1200
- if (existsSync2(localIndexPath)) {
1210
+ const localIndexPath = path3.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1211
+ if (existsSync3(localIndexPath)) {
1201
1212
  return localIndexPath;
1202
1213
  }
1203
1214
  if (hasProjectConfig(projectRoot)) {
@@ -1206,23 +1217,30 @@ function resolveProjectIndexPath(projectRoot, scope) {
1206
1217
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1207
1218
  }
1208
1219
 
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;
1220
+ // src/config/rebase.ts
1221
+ import * as path5 from "path";
1222
+
1223
+ // src/utils/paths.ts
1224
+ import * as path4 from "path";
1225
+ function normalizePathSeparators(value) {
1226
+ return value.replace(/\\/g, "/");
1227
+ }
1228
+ function isHiddenPathSegment(part) {
1229
+ return part.startsWith(".") && part !== "." && part !== "..";
1230
+ }
1231
+ function isBuildPathSegment(part) {
1232
+ return part.toLowerCase().includes("build");
1219
1233
  }
1220
- function normalizeRelativeConfigPath(candidate) {
1221
- return candidate.replace(/\\/g, "/");
1234
+ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1235
+ return relativePath.split(separator).some(
1236
+ (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1237
+ );
1222
1238
  }
1239
+
1240
+ // src/config/rebase.ts
1223
1241
  function isWithinRoot(rootDir, targetPath) {
1224
- const relativePath = path3.relative(rootDir, targetPath);
1225
- return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1242
+ const relativePath = path5.relative(rootDir, targetPath);
1243
+ return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
1226
1244
  }
1227
1245
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1228
1246
  if (!Array.isArray(values)) {
@@ -1233,22 +1251,106 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1233
1251
  if (!trimmed) {
1234
1252
  return trimmed;
1235
1253
  }
1236
- if (path3.isAbsolute(trimmed)) {
1254
+ if (path5.isAbsolute(trimmed)) {
1237
1255
  if (isWithinRoot(sourceRoot, trimmed)) {
1238
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1256
+ return normalizePathSeparators(path5.normalize(path5.relative(sourceRoot, trimmed) || "."));
1239
1257
  }
1240
- return path3.normalize(trimmed);
1258
+ return path5.normalize(trimmed);
1241
1259
  }
1242
- const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1260
+ const resolvedFromSource = path5.resolve(sourceRoot, trimmed);
1243
1261
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1244
- return normalizeRelativeConfigPath(path3.normalize(trimmed));
1262
+ return normalizePathSeparators(path5.normalize(trimmed));
1245
1263
  }
1246
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1264
+ return normalizePathSeparators(path5.normalize(path5.relative(targetRoot, resolvedFromSource)));
1247
1265
  }).filter(Boolean);
1248
1266
  }
1267
+
1268
+ // src/config/merger.ts
1269
+ var PROJECT_OVERRIDE_KEYS = [
1270
+ "embeddingProvider",
1271
+ "customProvider",
1272
+ "embeddingModel",
1273
+ "reranker",
1274
+ "include",
1275
+ "exclude",
1276
+ "indexing",
1277
+ "search",
1278
+ "debug",
1279
+ "scope"
1280
+ ];
1281
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1282
+ function isRecord(value) {
1283
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1284
+ }
1285
+ function isStringArray2(value) {
1286
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1287
+ }
1288
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1289
+ if (key in normalizedProjectConfig) {
1290
+ merged[key] = normalizedProjectConfig[key];
1291
+ return;
1292
+ }
1293
+ if (key in globalConfig) {
1294
+ merged[key] = globalConfig[key];
1295
+ }
1296
+ }
1297
+ function mergeUniqueStringArray(values) {
1298
+ return [...new Set(values.map((value) => String(value).trim()))];
1299
+ }
1300
+ function normalizeKnowledgeBasePath(value) {
1301
+ let normalized = path6.normalize(String(value).trim());
1302
+ const root = path6.parse(normalized).root;
1303
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1304
+ normalized = normalized.slice(0, -1);
1305
+ }
1306
+ return normalized;
1307
+ }
1308
+ function mergeKnowledgeBasePaths(values) {
1309
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1310
+ }
1311
+ function validateConfigLayerShape(rawConfig, filePath) {
1312
+ if (!isRecord(rawConfig)) {
1313
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1314
+ }
1315
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1316
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1317
+ }
1318
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1319
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1320
+ }
1321
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1322
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1323
+ }
1324
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1325
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1326
+ }
1327
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1328
+ const value = rawConfig[section];
1329
+ if (value !== void 0 && !isRecord(value)) {
1330
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1331
+ }
1332
+ }
1333
+ return rawConfig;
1334
+ }
1335
+ function loadJsonFile(filePath) {
1336
+ if (!existsSync4(filePath)) {
1337
+ return null;
1338
+ }
1339
+ try {
1340
+ const content = readFileSync3(filePath, "utf-8");
1341
+ return validateConfigLayerShape(JSON.parse(content), filePath);
1342
+ } catch (error) {
1343
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
1344
+ throw error;
1345
+ }
1346
+ const message = error instanceof Error ? error.message : String(error);
1347
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
1348
+ }
1349
+ return null;
1350
+ }
1249
1351
  function materializeLocalProjectConfig(projectRoot, config) {
1250
- const localConfigPath = path3.join(projectRoot, ".opencode", "codebase-index.json");
1251
- mkdirSync(path3.dirname(localConfigPath), { recursive: true });
1352
+ const localConfigPath = path6.join(projectRoot, ".opencode", "codebase-index.json");
1353
+ mkdirSync(path6.dirname(localConfigPath), { recursive: true });
1252
1354
  writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1253
1355
  return localConfigPath;
1254
1356
  }
@@ -1259,7 +1361,7 @@ function loadProjectConfigLayer(projectRoot) {
1259
1361
  return {};
1260
1362
  }
1261
1363
  const normalizedConfig = { ...projectConfig };
1262
- const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1364
+ const projectConfigBaseDir = path6.dirname(path6.dirname(projectConfigPath));
1263
1365
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
1264
1366
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1265
1367
  normalizedConfig.knowledgeBases,
@@ -1271,10 +1373,22 @@ function loadProjectConfigLayer(projectRoot) {
1271
1373
  }
1272
1374
  function loadMergedConfig(projectRoot) {
1273
1375
  const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1274
- const globalConfig = loadJsonFile(globalConfigPath);
1275
1376
  const projectConfigPath = resolveProjectConfigPath(projectRoot);
1377
+ let globalConfig = null;
1378
+ let globalConfigError = null;
1379
+ try {
1380
+ globalConfig = loadJsonFile(globalConfigPath);
1381
+ } catch (error) {
1382
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
1383
+ }
1276
1384
  const projectConfig = loadJsonFile(projectConfigPath);
1277
1385
  const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1386
+ if (globalConfigError) {
1387
+ if (!projectConfig) {
1388
+ throw globalConfigError;
1389
+ }
1390
+ globalConfig = null;
1391
+ }
1278
1392
  if (!globalConfig && !projectConfig) {
1279
1393
  return {};
1280
1394
  }
@@ -1284,60 +1398,16 @@ function loadMergedConfig(projectRoot) {
1284
1398
  if (!globalConfig && projectConfig) {
1285
1399
  return normalizedProjectConfig;
1286
1400
  }
1401
+ if (!globalConfig || !projectConfig) {
1402
+ return globalConfig ?? normalizedProjectConfig;
1403
+ }
1287
1404
  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;
1405
+ for (const key of PROJECT_OVERRIDE_KEYS) {
1406
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1337
1407
  }
1338
1408
  if (projectConfig) {
1339
1409
  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") {
1410
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1341
1411
  continue;
1342
1412
  }
1343
1413
  merged[key] = normalizedProjectConfig[key];
@@ -1346,13 +1416,11 @@ function loadMergedConfig(projectRoot) {
1346
1416
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1347
1417
  const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1348
1418
  const allKbs = [...globalKbs, ...projectKbs];
1349
- const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1350
- merged.knowledgeBases = uniqueKbs;
1419
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1351
1420
  const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1352
1421
  const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1353
1422
  const allAdditional = [...globalAdditional, ...projectAdditional];
1354
- const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
1355
- merged.additionalInclude = uniqueAdditional;
1423
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1356
1424
  return merged;
1357
1425
  }
1358
1426
 
@@ -1446,7 +1514,7 @@ var ReaddirpStream = class extends Readable {
1446
1514
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1447
1515
  const statMethod = opts.lstat ? lstat : stat;
1448
1516
  if (wantBigintFsStats) {
1449
- this._stat = (path12) => statMethod(path12, { bigint: true });
1517
+ this._stat = (path18) => statMethod(path18, { bigint: true });
1450
1518
  } else {
1451
1519
  this._stat = statMethod;
1452
1520
  }
@@ -1471,8 +1539,8 @@ var ReaddirpStream = class extends Readable {
1471
1539
  const par = this.parent;
1472
1540
  const fil = par && par.files;
1473
1541
  if (fil && fil.length > 0) {
1474
- const { path: path12, depth } = par;
1475
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1542
+ const { path: path18, depth } = par;
1543
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1476
1544
  const awaited = await Promise.all(slice);
1477
1545
  for (const entry of awaited) {
1478
1546
  if (!entry)
@@ -1512,20 +1580,20 @@ var ReaddirpStream = class extends Readable {
1512
1580
  this.reading = false;
1513
1581
  }
1514
1582
  }
1515
- async _exploreDir(path12, depth) {
1583
+ async _exploreDir(path18, depth) {
1516
1584
  let files;
1517
1585
  try {
1518
- files = await readdir(path12, this._rdOptions);
1586
+ files = await readdir(path18, this._rdOptions);
1519
1587
  } catch (error) {
1520
1588
  this._onError(error);
1521
1589
  }
1522
- return { files, depth, path: path12 };
1590
+ return { files, depth, path: path18 };
1523
1591
  }
1524
- async _formatEntry(dirent, path12) {
1592
+ async _formatEntry(dirent, path18) {
1525
1593
  let entry;
1526
1594
  const basename5 = this._isDirent ? dirent.name : dirent;
1527
1595
  try {
1528
- const fullPath = presolve(pjoin(path12, basename5));
1596
+ const fullPath = presolve(pjoin(path18, basename5));
1529
1597
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
1530
1598
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1531
1599
  } catch (err) {
@@ -1925,16 +1993,16 @@ var delFromSet = (main, prop, item) => {
1925
1993
  };
1926
1994
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1927
1995
  var FsWatchInstances = /* @__PURE__ */ new Map();
1928
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
1996
+ function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
1929
1997
  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));
1998
+ listener(path18);
1999
+ emitRaw(rawEvent, evPath, { watchedPath: path18 });
2000
+ if (evPath && path18 !== evPath) {
2001
+ fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
1934
2002
  }
1935
2003
  };
1936
2004
  try {
1937
- return fs_watch(path12, {
2005
+ return fs_watch(path18, {
1938
2006
  persistent: options.persistent
1939
2007
  }, handleEvent);
1940
2008
  } catch (error) {
@@ -1950,12 +2018,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1950
2018
  listener(val1, val2, val3);
1951
2019
  });
1952
2020
  };
1953
- var setFsWatchListener = (path12, fullPath, options, handlers) => {
2021
+ var setFsWatchListener = (path18, fullPath, options, handlers) => {
1954
2022
  const { listener, errHandler, rawEmitter } = handlers;
1955
2023
  let cont = FsWatchInstances.get(fullPath);
1956
2024
  let watcher;
1957
2025
  if (!options.persistent) {
1958
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
2026
+ watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
1959
2027
  if (!watcher)
1960
2028
  return;
1961
2029
  return watcher.close.bind(watcher);
@@ -1966,7 +2034,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1966
2034
  addAndConvert(cont, KEY_RAW, rawEmitter);
1967
2035
  } else {
1968
2036
  watcher = createFsWatchInstance(
1969
- path12,
2037
+ path18,
1970
2038
  options,
1971
2039
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1972
2040
  errHandler,
@@ -1981,7 +2049,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1981
2049
  cont.watcherUnusable = true;
1982
2050
  if (isWindows && error.code === "EPERM") {
1983
2051
  try {
1984
- const fd = await open(path12, "r");
2052
+ const fd = await open(path18, "r");
1985
2053
  await fd.close();
1986
2054
  broadcastErr(error);
1987
2055
  } catch (err) {
@@ -2012,7 +2080,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
2012
2080
  };
2013
2081
  };
2014
2082
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2015
- var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2083
+ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2016
2084
  const { listener, rawEmitter } = handlers;
2017
2085
  let cont = FsWatchFileInstances.get(fullPath);
2018
2086
  const copts = cont && cont.options;
@@ -2034,7 +2102,7 @@ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2034
2102
  });
2035
2103
  const currmtime = curr.mtimeMs;
2036
2104
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2037
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
2105
+ foreach(cont.listeners, (listener2) => listener2(path18, curr));
2038
2106
  }
2039
2107
  })
2040
2108
  };
@@ -2064,13 +2132,13 @@ var NodeFsHandler = class {
2064
2132
  * @param listener on fs change
2065
2133
  * @returns closer for the watcher instance
2066
2134
  */
2067
- _watchWithNodeFs(path12, listener) {
2135
+ _watchWithNodeFs(path18, listener) {
2068
2136
  const opts = this.fsw.options;
2069
- const directory = sp.dirname(path12);
2070
- const basename5 = sp.basename(path12);
2137
+ const directory = sp.dirname(path18);
2138
+ const basename5 = sp.basename(path18);
2071
2139
  const parent = this.fsw._getWatchedDir(directory);
2072
2140
  parent.add(basename5);
2073
- const absolutePath = sp.resolve(path12);
2141
+ const absolutePath = sp.resolve(path18);
2074
2142
  const options = {
2075
2143
  persistent: opts.persistent
2076
2144
  };
@@ -2080,12 +2148,12 @@ var NodeFsHandler = class {
2080
2148
  if (opts.usePolling) {
2081
2149
  const enableBin = opts.interval !== opts.binaryInterval;
2082
2150
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2083
- closer = setFsWatchFileListener(path12, absolutePath, options, {
2151
+ closer = setFsWatchFileListener(path18, absolutePath, options, {
2084
2152
  listener,
2085
2153
  rawEmitter: this.fsw._emitRaw
2086
2154
  });
2087
2155
  } else {
2088
- closer = setFsWatchListener(path12, absolutePath, options, {
2156
+ closer = setFsWatchListener(path18, absolutePath, options, {
2089
2157
  listener,
2090
2158
  errHandler: this._boundHandleError,
2091
2159
  rawEmitter: this.fsw._emitRaw
@@ -2101,13 +2169,13 @@ var NodeFsHandler = class {
2101
2169
  if (this.fsw.closed) {
2102
2170
  return;
2103
2171
  }
2104
- const dirname8 = sp.dirname(file);
2172
+ const dirname9 = sp.dirname(file);
2105
2173
  const basename5 = sp.basename(file);
2106
- const parent = this.fsw._getWatchedDir(dirname8);
2174
+ const parent = this.fsw._getWatchedDir(dirname9);
2107
2175
  let prevStats = stats;
2108
2176
  if (parent.has(basename5))
2109
2177
  return;
2110
- const listener = async (path12, newStats) => {
2178
+ const listener = async (path18, newStats) => {
2111
2179
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2112
2180
  return;
2113
2181
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2121,16 +2189,16 @@ var NodeFsHandler = class {
2121
2189
  this.fsw._emit(EV.CHANGE, file, newStats2);
2122
2190
  }
2123
2191
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2124
- this.fsw._closeFile(path12);
2192
+ this.fsw._closeFile(path18);
2125
2193
  prevStats = newStats2;
2126
2194
  const closer2 = this._watchWithNodeFs(file, listener);
2127
2195
  if (closer2)
2128
- this.fsw._addPathCloser(path12, closer2);
2196
+ this.fsw._addPathCloser(path18, closer2);
2129
2197
  } else {
2130
2198
  prevStats = newStats2;
2131
2199
  }
2132
2200
  } catch (error) {
2133
- this.fsw._remove(dirname8, basename5);
2201
+ this.fsw._remove(dirname9, basename5);
2134
2202
  }
2135
2203
  } else if (parent.has(basename5)) {
2136
2204
  const at = newStats.atimeMs;
@@ -2157,7 +2225,7 @@ var NodeFsHandler = class {
2157
2225
  * @param item basename of this item
2158
2226
  * @returns true if no more processing is needed for this entry.
2159
2227
  */
2160
- async _handleSymlink(entry, directory, path12, item) {
2228
+ async _handleSymlink(entry, directory, path18, item) {
2161
2229
  if (this.fsw.closed) {
2162
2230
  return;
2163
2231
  }
@@ -2167,7 +2235,7 @@ var NodeFsHandler = class {
2167
2235
  this.fsw._incrReadyCount();
2168
2236
  let linkPath;
2169
2237
  try {
2170
- linkPath = await fsrealpath(path12);
2238
+ linkPath = await fsrealpath(path18);
2171
2239
  } catch (e) {
2172
2240
  this.fsw._emitReady();
2173
2241
  return true;
@@ -2177,12 +2245,12 @@ var NodeFsHandler = class {
2177
2245
  if (dir.has(item)) {
2178
2246
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2179
2247
  this.fsw._symlinkPaths.set(full, linkPath);
2180
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
2248
+ this.fsw._emit(EV.CHANGE, path18, entry.stats);
2181
2249
  }
2182
2250
  } else {
2183
2251
  dir.add(item);
2184
2252
  this.fsw._symlinkPaths.set(full, linkPath);
2185
- this.fsw._emit(EV.ADD, path12, entry.stats);
2253
+ this.fsw._emit(EV.ADD, path18, entry.stats);
2186
2254
  }
2187
2255
  this.fsw._emitReady();
2188
2256
  return true;
@@ -2212,9 +2280,9 @@ var NodeFsHandler = class {
2212
2280
  return;
2213
2281
  }
2214
2282
  const item = entry.path;
2215
- let path12 = sp.join(directory, item);
2283
+ let path18 = sp.join(directory, item);
2216
2284
  current.add(item);
2217
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
2285
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2218
2286
  return;
2219
2287
  }
2220
2288
  if (this.fsw.closed) {
@@ -2223,11 +2291,11 @@ var NodeFsHandler = class {
2223
2291
  }
2224
2292
  if (item === target || !target && !previous.has(item)) {
2225
2293
  this.fsw._incrReadyCount();
2226
- path12 = sp.join(dir, sp.relative(dir, path12));
2227
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
2294
+ path18 = sp.join(dir, sp.relative(dir, path18));
2295
+ this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2228
2296
  }
2229
2297
  }).on(EV.ERROR, this._boundHandleError);
2230
- return new Promise((resolve9, reject) => {
2298
+ return new Promise((resolve11, reject) => {
2231
2299
  if (!stream)
2232
2300
  return reject();
2233
2301
  stream.once(STR_END, () => {
@@ -2236,7 +2304,7 @@ var NodeFsHandler = class {
2236
2304
  return;
2237
2305
  }
2238
2306
  const wasThrottled = throttler ? throttler.clear() : false;
2239
- resolve9(void 0);
2307
+ resolve11(void 0);
2240
2308
  previous.getChildren().filter((item) => {
2241
2309
  return item !== directory && !current.has(item);
2242
2310
  }).forEach((item) => {
@@ -2293,13 +2361,13 @@ var NodeFsHandler = class {
2293
2361
  * @param depth Child path actually targeted for watch
2294
2362
  * @param target Child path actually targeted for watch
2295
2363
  */
2296
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2364
+ async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2297
2365
  const ready = this.fsw._emitReady;
2298
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2366
+ if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2299
2367
  ready();
2300
2368
  return false;
2301
2369
  }
2302
- const wh = this.fsw._getWatchHelpers(path12);
2370
+ const wh = this.fsw._getWatchHelpers(path18);
2303
2371
  if (priorWh) {
2304
2372
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2305
2373
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2315,8 +2383,8 @@ var NodeFsHandler = class {
2315
2383
  const follow = this.fsw.options.followSymlinks;
2316
2384
  let closer;
2317
2385
  if (stats.isDirectory()) {
2318
- const absPath = sp.resolve(path12);
2319
- const targetPath = follow ? await fsrealpath(path12) : path12;
2386
+ const absPath = sp.resolve(path18);
2387
+ const targetPath = follow ? await fsrealpath(path18) : path18;
2320
2388
  if (this.fsw.closed)
2321
2389
  return;
2322
2390
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2326,29 +2394,29 @@ var NodeFsHandler = class {
2326
2394
  this.fsw._symlinkPaths.set(absPath, targetPath);
2327
2395
  }
2328
2396
  } else if (stats.isSymbolicLink()) {
2329
- const targetPath = follow ? await fsrealpath(path12) : path12;
2397
+ const targetPath = follow ? await fsrealpath(path18) : path18;
2330
2398
  if (this.fsw.closed)
2331
2399
  return;
2332
2400
  const parent = sp.dirname(wh.watchPath);
2333
2401
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2334
2402
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2335
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2403
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2336
2404
  if (this.fsw.closed)
2337
2405
  return;
2338
2406
  if (targetPath !== void 0) {
2339
- this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2407
+ this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2340
2408
  }
2341
2409
  } else {
2342
2410
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2343
2411
  }
2344
2412
  ready();
2345
2413
  if (closer)
2346
- this.fsw._addPathCloser(path12, closer);
2414
+ this.fsw._addPathCloser(path18, closer);
2347
2415
  return false;
2348
2416
  } catch (error) {
2349
2417
  if (this.fsw._handleError(error)) {
2350
2418
  ready();
2351
- return path12;
2419
+ return path18;
2352
2420
  }
2353
2421
  }
2354
2422
  }
@@ -2391,24 +2459,24 @@ function createPattern(matcher) {
2391
2459
  }
2392
2460
  return () => false;
2393
2461
  }
2394
- function normalizePath(path12) {
2395
- if (typeof path12 !== "string")
2462
+ function normalizePath(path18) {
2463
+ if (typeof path18 !== "string")
2396
2464
  throw new Error("string expected");
2397
- path12 = sp2.normalize(path12);
2398
- path12 = path12.replace(/\\/g, "/");
2465
+ path18 = sp2.normalize(path18);
2466
+ path18 = path18.replace(/\\/g, "/");
2399
2467
  let prepend = false;
2400
- if (path12.startsWith("//"))
2468
+ if (path18.startsWith("//"))
2401
2469
  prepend = true;
2402
- path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2470
+ path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2403
2471
  if (prepend)
2404
- path12 = "/" + path12;
2405
- return path12;
2472
+ path18 = "/" + path18;
2473
+ return path18;
2406
2474
  }
2407
2475
  function matchPatterns(patterns, testString, stats) {
2408
- const path12 = normalizePath(testString);
2476
+ const path18 = normalizePath(testString);
2409
2477
  for (let index = 0; index < patterns.length; index++) {
2410
2478
  const pattern = patterns[index];
2411
- if (pattern(path12, stats)) {
2479
+ if (pattern(path18, stats)) {
2412
2480
  return true;
2413
2481
  }
2414
2482
  }
@@ -2446,19 +2514,19 @@ var toUnix = (string) => {
2446
2514
  }
2447
2515
  return str;
2448
2516
  };
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));
2517
+ var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2518
+ var normalizeIgnored = (cwd = "") => (path18) => {
2519
+ if (typeof path18 === "string") {
2520
+ return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2453
2521
  } else {
2454
- return path12;
2522
+ return path18;
2455
2523
  }
2456
2524
  };
2457
- var getAbsolutePath = (path12, cwd) => {
2458
- if (sp2.isAbsolute(path12)) {
2459
- return path12;
2525
+ var getAbsolutePath = (path18, cwd) => {
2526
+ if (sp2.isAbsolute(path18)) {
2527
+ return path18;
2460
2528
  }
2461
- return sp2.join(cwd, path12);
2529
+ return sp2.join(cwd, path18);
2462
2530
  };
2463
2531
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2464
2532
  var DirEntry = class {
@@ -2523,10 +2591,10 @@ var WatchHelper = class {
2523
2591
  dirParts;
2524
2592
  followSymlinks;
2525
2593
  statMethod;
2526
- constructor(path12, follow, fsw) {
2594
+ constructor(path18, follow, fsw) {
2527
2595
  this.fsw = fsw;
2528
- const watchPath = path12;
2529
- this.path = path12 = path12.replace(REPLACER_RE, "");
2596
+ const watchPath = path18;
2597
+ this.path = path18 = path18.replace(REPLACER_RE, "");
2530
2598
  this.watchPath = watchPath;
2531
2599
  this.fullWatchPath = sp2.resolve(watchPath);
2532
2600
  this.dirParts = [];
@@ -2666,20 +2734,20 @@ var FSWatcher = class extends EventEmitter {
2666
2734
  this._closePromise = void 0;
2667
2735
  let paths = unifyPaths(paths_);
2668
2736
  if (cwd) {
2669
- paths = paths.map((path12) => {
2670
- const absPath = getAbsolutePath(path12, cwd);
2737
+ paths = paths.map((path18) => {
2738
+ const absPath = getAbsolutePath(path18, cwd);
2671
2739
  return absPath;
2672
2740
  });
2673
2741
  }
2674
- paths.forEach((path12) => {
2675
- this._removeIgnoredPath(path12);
2742
+ paths.forEach((path18) => {
2743
+ this._removeIgnoredPath(path18);
2676
2744
  });
2677
2745
  this._userIgnored = void 0;
2678
2746
  if (!this._readyCount)
2679
2747
  this._readyCount = 0;
2680
2748
  this._readyCount += paths.length;
2681
- Promise.all(paths.map(async (path12) => {
2682
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2749
+ Promise.all(paths.map(async (path18) => {
2750
+ const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2683
2751
  if (res)
2684
2752
  this._emitReady();
2685
2753
  return res;
@@ -2701,17 +2769,17 @@ var FSWatcher = class extends EventEmitter {
2701
2769
  return this;
2702
2770
  const paths = unifyPaths(paths_);
2703
2771
  const { cwd } = this.options;
2704
- paths.forEach((path12) => {
2705
- if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2772
+ paths.forEach((path18) => {
2773
+ if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2706
2774
  if (cwd)
2707
- path12 = sp2.join(cwd, path12);
2708
- path12 = sp2.resolve(path12);
2775
+ path18 = sp2.join(cwd, path18);
2776
+ path18 = sp2.resolve(path18);
2709
2777
  }
2710
- this._closePath(path12);
2711
- this._addIgnoredPath(path12);
2712
- if (this._watched.has(path12)) {
2778
+ this._closePath(path18);
2779
+ this._addIgnoredPath(path18);
2780
+ if (this._watched.has(path18)) {
2713
2781
  this._addIgnoredPath({
2714
- path: path12,
2782
+ path: path18,
2715
2783
  recursive: true
2716
2784
  });
2717
2785
  }
@@ -2775,38 +2843,38 @@ var FSWatcher = class extends EventEmitter {
2775
2843
  * @param stats arguments to be passed with event
2776
2844
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2777
2845
  */
2778
- async _emit(event, path12, stats) {
2846
+ async _emit(event, path18, stats) {
2779
2847
  if (this.closed)
2780
2848
  return;
2781
2849
  const opts = this.options;
2782
2850
  if (isWindows)
2783
- path12 = sp2.normalize(path12);
2851
+ path18 = sp2.normalize(path18);
2784
2852
  if (opts.cwd)
2785
- path12 = sp2.relative(opts.cwd, path12);
2786
- const args = [path12];
2853
+ path18 = sp2.relative(opts.cwd, path18);
2854
+ const args = [path18];
2787
2855
  if (stats != null)
2788
2856
  args.push(stats);
2789
2857
  const awf = opts.awaitWriteFinish;
2790
2858
  let pw;
2791
- if (awf && (pw = this._pendingWrites.get(path12))) {
2859
+ if (awf && (pw = this._pendingWrites.get(path18))) {
2792
2860
  pw.lastChange = /* @__PURE__ */ new Date();
2793
2861
  return this;
2794
2862
  }
2795
2863
  if (opts.atomic) {
2796
2864
  if (event === EVENTS.UNLINK) {
2797
- this._pendingUnlinks.set(path12, [event, ...args]);
2865
+ this._pendingUnlinks.set(path18, [event, ...args]);
2798
2866
  setTimeout(() => {
2799
- this._pendingUnlinks.forEach((entry, path13) => {
2867
+ this._pendingUnlinks.forEach((entry, path19) => {
2800
2868
  this.emit(...entry);
2801
2869
  this.emit(EVENTS.ALL, ...entry);
2802
- this._pendingUnlinks.delete(path13);
2870
+ this._pendingUnlinks.delete(path19);
2803
2871
  });
2804
2872
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2805
2873
  return this;
2806
2874
  }
2807
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2875
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2808
2876
  event = EVENTS.CHANGE;
2809
- this._pendingUnlinks.delete(path12);
2877
+ this._pendingUnlinks.delete(path18);
2810
2878
  }
2811
2879
  }
2812
2880
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2824,16 +2892,16 @@ var FSWatcher = class extends EventEmitter {
2824
2892
  this.emitWithAll(event, args);
2825
2893
  }
2826
2894
  };
2827
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2895
+ this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2828
2896
  return this;
2829
2897
  }
2830
2898
  if (event === EVENTS.CHANGE) {
2831
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2899
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2832
2900
  if (isThrottled)
2833
2901
  return this;
2834
2902
  }
2835
2903
  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;
2904
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2837
2905
  let stats2;
2838
2906
  try {
2839
2907
  stats2 = await stat3(fullPath);
@@ -2864,23 +2932,23 @@ var FSWatcher = class extends EventEmitter {
2864
2932
  * @param timeout duration of time to suppress duplicate actions
2865
2933
  * @returns tracking object or false if action should be suppressed
2866
2934
  */
2867
- _throttle(actionType, path12, timeout) {
2935
+ _throttle(actionType, path18, timeout) {
2868
2936
  if (!this._throttled.has(actionType)) {
2869
2937
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2870
2938
  }
2871
2939
  const action = this._throttled.get(actionType);
2872
2940
  if (!action)
2873
2941
  throw new Error("invalid throttle");
2874
- const actionPath = action.get(path12);
2942
+ const actionPath = action.get(path18);
2875
2943
  if (actionPath) {
2876
2944
  actionPath.count++;
2877
2945
  return false;
2878
2946
  }
2879
2947
  let timeoutObject;
2880
2948
  const clear = () => {
2881
- const item = action.get(path12);
2949
+ const item = action.get(path18);
2882
2950
  const count = item ? item.count : 0;
2883
- action.delete(path12);
2951
+ action.delete(path18);
2884
2952
  clearTimeout(timeoutObject);
2885
2953
  if (item)
2886
2954
  clearTimeout(item.timeoutObject);
@@ -2888,7 +2956,7 @@ var FSWatcher = class extends EventEmitter {
2888
2956
  };
2889
2957
  timeoutObject = setTimeout(clear, timeout);
2890
2958
  const thr = { timeoutObject, clear, count: 0 };
2891
- action.set(path12, thr);
2959
+ action.set(path18, thr);
2892
2960
  return thr;
2893
2961
  }
2894
2962
  _incrReadyCount() {
@@ -2902,44 +2970,44 @@ var FSWatcher = class extends EventEmitter {
2902
2970
  * @param event
2903
2971
  * @param awfEmit Callback to be called when ready for event to be emitted.
2904
2972
  */
2905
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
2973
+ _awaitWriteFinish(path18, threshold, event, awfEmit) {
2906
2974
  const awf = this.options.awaitWriteFinish;
2907
2975
  if (typeof awf !== "object")
2908
2976
  return;
2909
2977
  const pollInterval = awf.pollInterval;
2910
2978
  let timeoutHandler;
2911
- let fullPath = path12;
2912
- if (this.options.cwd && !sp2.isAbsolute(path12)) {
2913
- fullPath = sp2.join(this.options.cwd, path12);
2979
+ let fullPath = path18;
2980
+ if (this.options.cwd && !sp2.isAbsolute(path18)) {
2981
+ fullPath = sp2.join(this.options.cwd, path18);
2914
2982
  }
2915
2983
  const now = /* @__PURE__ */ new Date();
2916
2984
  const writes = this._pendingWrites;
2917
2985
  function awaitWriteFinishFn(prevStat) {
2918
2986
  statcb(fullPath, (err, curStat) => {
2919
- if (err || !writes.has(path12)) {
2987
+ if (err || !writes.has(path18)) {
2920
2988
  if (err && err.code !== "ENOENT")
2921
2989
  awfEmit(err);
2922
2990
  return;
2923
2991
  }
2924
2992
  const now2 = Number(/* @__PURE__ */ new Date());
2925
2993
  if (prevStat && curStat.size !== prevStat.size) {
2926
- writes.get(path12).lastChange = now2;
2994
+ writes.get(path18).lastChange = now2;
2927
2995
  }
2928
- const pw = writes.get(path12);
2996
+ const pw = writes.get(path18);
2929
2997
  const df = now2 - pw.lastChange;
2930
2998
  if (df >= threshold) {
2931
- writes.delete(path12);
2999
+ writes.delete(path18);
2932
3000
  awfEmit(void 0, curStat);
2933
3001
  } else {
2934
3002
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2935
3003
  }
2936
3004
  });
2937
3005
  }
2938
- if (!writes.has(path12)) {
2939
- writes.set(path12, {
3006
+ if (!writes.has(path18)) {
3007
+ writes.set(path18, {
2940
3008
  lastChange: now,
2941
3009
  cancelWait: () => {
2942
- writes.delete(path12);
3010
+ writes.delete(path18);
2943
3011
  clearTimeout(timeoutHandler);
2944
3012
  return event;
2945
3013
  }
@@ -2950,8 +3018,8 @@ var FSWatcher = class extends EventEmitter {
2950
3018
  /**
2951
3019
  * Determines whether user has asked to ignore this path.
2952
3020
  */
2953
- _isIgnored(path12, stats) {
2954
- if (this.options.atomic && DOT_RE.test(path12))
3021
+ _isIgnored(path18, stats) {
3022
+ if (this.options.atomic && DOT_RE.test(path18))
2955
3023
  return true;
2956
3024
  if (!this._userIgnored) {
2957
3025
  const { cwd } = this.options;
@@ -2961,17 +3029,17 @@ var FSWatcher = class extends EventEmitter {
2961
3029
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2962
3030
  this._userIgnored = anymatch(list, void 0);
2963
3031
  }
2964
- return this._userIgnored(path12, stats);
3032
+ return this._userIgnored(path18, stats);
2965
3033
  }
2966
- _isntIgnored(path12, stat4) {
2967
- return !this._isIgnored(path12, stat4);
3034
+ _isntIgnored(path18, stat4) {
3035
+ return !this._isIgnored(path18, stat4);
2968
3036
  }
2969
3037
  /**
2970
3038
  * Provides a set of common helpers and properties relating to symlink handling.
2971
3039
  * @param path file or directory pattern being watched
2972
3040
  */
2973
- _getWatchHelpers(path12) {
2974
- return new WatchHelper(path12, this.options.followSymlinks, this);
3041
+ _getWatchHelpers(path18) {
3042
+ return new WatchHelper(path18, this.options.followSymlinks, this);
2975
3043
  }
2976
3044
  // Directory helpers
2977
3045
  // -----------------
@@ -3003,63 +3071,63 @@ var FSWatcher = class extends EventEmitter {
3003
3071
  * @param item base path of item/directory
3004
3072
  */
3005
3073
  _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))
3074
+ const path18 = sp2.join(directory, item);
3075
+ const fullPath = sp2.resolve(path18);
3076
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3077
+ if (!this._throttle("remove", path18, 100))
3010
3078
  return;
3011
3079
  if (!isDirectory && this._watched.size === 1) {
3012
3080
  this.add(directory, item, true);
3013
3081
  }
3014
- const wp = this._getWatchedDir(path12);
3082
+ const wp = this._getWatchedDir(path18);
3015
3083
  const nestedDirectoryChildren = wp.getChildren();
3016
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
3084
+ nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3017
3085
  const parent = this._getWatchedDir(directory);
3018
3086
  const wasTracked = parent.has(item);
3019
3087
  parent.remove(item);
3020
3088
  if (this._symlinkPaths.has(fullPath)) {
3021
3089
  this._symlinkPaths.delete(fullPath);
3022
3090
  }
3023
- let relPath = path12;
3091
+ let relPath = path18;
3024
3092
  if (this.options.cwd)
3025
- relPath = sp2.relative(this.options.cwd, path12);
3093
+ relPath = sp2.relative(this.options.cwd, path18);
3026
3094
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3027
3095
  const event = this._pendingWrites.get(relPath).cancelWait();
3028
3096
  if (event === EVENTS.ADD)
3029
3097
  return;
3030
3098
  }
3031
- this._watched.delete(path12);
3099
+ this._watched.delete(path18);
3032
3100
  this._watched.delete(fullPath);
3033
3101
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3034
- if (wasTracked && !this._isIgnored(path12))
3035
- this._emit(eventName, path12);
3036
- this._closePath(path12);
3102
+ if (wasTracked && !this._isIgnored(path18))
3103
+ this._emit(eventName, path18);
3104
+ this._closePath(path18);
3037
3105
  }
3038
3106
  /**
3039
3107
  * Closes all watchers for a path
3040
3108
  */
3041
- _closePath(path12) {
3042
- this._closeFile(path12);
3043
- const dir = sp2.dirname(path12);
3044
- this._getWatchedDir(dir).remove(sp2.basename(path12));
3109
+ _closePath(path18) {
3110
+ this._closeFile(path18);
3111
+ const dir = sp2.dirname(path18);
3112
+ this._getWatchedDir(dir).remove(sp2.basename(path18));
3045
3113
  }
3046
3114
  /**
3047
3115
  * Closes only file-specific watchers
3048
3116
  */
3049
- _closeFile(path12) {
3050
- const closers = this._closers.get(path12);
3117
+ _closeFile(path18) {
3118
+ const closers = this._closers.get(path18);
3051
3119
  if (!closers)
3052
3120
  return;
3053
3121
  closers.forEach((closer) => closer());
3054
- this._closers.delete(path12);
3122
+ this._closers.delete(path18);
3055
3123
  }
3056
- _addPathCloser(path12, closer) {
3124
+ _addPathCloser(path18, closer) {
3057
3125
  if (!closer)
3058
3126
  return;
3059
- let list = this._closers.get(path12);
3127
+ let list = this._closers.get(path18);
3060
3128
  if (!list) {
3061
3129
  list = [];
3062
- this._closers.set(path12, list);
3130
+ this._closers.set(path18, list);
3063
3131
  }
3064
3132
  list.push(closer);
3065
3133
  }
@@ -3088,13 +3156,13 @@ function watch(paths, options = {}) {
3088
3156
  }
3089
3157
  var chokidar_default = { watch, FSWatcher };
3090
3158
 
3091
- // src/watcher/index.ts
3092
- import * as path5 from "path";
3159
+ // src/watcher/file-watcher.ts
3160
+ import * as path8 from "path";
3093
3161
 
3094
3162
  // src/utils/files.ts
3095
3163
  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";
3164
+ import { existsSync as existsSync5, readFileSync as readFileSync4, promises as fsPromises } from "fs";
3165
+ import * as path7 from "path";
3098
3166
  var PROJECT_MARKERS = [
3099
3167
  ".git",
3100
3168
  "package.json",
@@ -3113,7 +3181,7 @@ var PROJECT_MARKERS = [
3113
3181
  ];
3114
3182
  function hasProjectMarker(projectRoot) {
3115
3183
  for (const marker of PROJECT_MARKERS) {
3116
- if (existsSync4(path4.join(projectRoot, marker))) {
3184
+ if (existsSync5(path7.join(projectRoot, marker))) {
3117
3185
  return true;
3118
3186
  }
3119
3187
  }
@@ -3139,23 +3207,17 @@ function createIgnoreFilter(projectRoot) {
3139
3207
  "**/*build*/**"
3140
3208
  ];
3141
3209
  ig.add(defaultIgnores);
3142
- const gitignorePath = path4.join(projectRoot, ".gitignore");
3143
- if (existsSync4(gitignorePath)) {
3144
- const gitignoreContent = readFileSync3(gitignorePath, "utf-8");
3210
+ const gitignorePath = path7.join(projectRoot, ".gitignore");
3211
+ if (existsSync5(gitignorePath)) {
3212
+ const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
3145
3213
  ig.add(gitignoreContent);
3146
3214
  }
3147
3215
  return ig;
3148
3216
  }
3149
3217
  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
- }
3218
+ const relativePath = path7.relative(projectRoot, filePath);
3219
+ if (hasFilteredPathSegment(relativePath, path7.sep)) {
3220
+ return false;
3159
3221
  }
3160
3222
  if (ignoreFilter.ignores(relativePath)) {
3161
3223
  return false;
@@ -3192,15 +3254,15 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3192
3254
  const filesInDir = [];
3193
3255
  const subdirs = [];
3194
3256
  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 !== "..") {
3257
+ const fullPath = path7.join(dir, entry.name);
3258
+ const relativePath = path7.relative(projectRoot, fullPath);
3259
+ if (isHiddenPathSegment(entry.name)) {
3198
3260
  if (entry.isDirectory()) {
3199
3261
  skipped.push({ path: relativePath, reason: "excluded" });
3200
3262
  }
3201
3263
  continue;
3202
3264
  }
3203
- if (entry.isDirectory() && entry.name.toLowerCase().includes("build")) {
3265
+ if (entry.isDirectory() && isBuildPathSegment(entry.name)) {
3204
3266
  skipped.push({ path: relativePath, reason: "excluded" });
3205
3267
  continue;
3206
3268
  }
@@ -3242,7 +3304,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3242
3304
  yield f;
3243
3305
  }
3244
3306
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3245
- skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3307
+ skipped.push({ path: path7.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3246
3308
  }
3247
3309
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3248
3310
  if (canRecurse) {
@@ -3282,8 +3344,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3282
3344
  if (additionalRoots && additionalRoots.length > 0) {
3283
3345
  const normalizedRoots = /* @__PURE__ */ new Set();
3284
3346
  for (const kbRoot of additionalRoots) {
3285
- const resolved = path4.normalize(
3286
- path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3347
+ const resolved = path7.normalize(
3348
+ path7.isAbsolute(kbRoot) ? kbRoot : path7.resolve(projectRoot, kbRoot)
3287
3349
  );
3288
3350
  normalizedRoots.add(resolved);
3289
3351
  }
@@ -3316,7 +3378,7 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3316
3378
  return { files, skipped };
3317
3379
  }
3318
3380
 
3319
- // src/watcher/index.ts
3381
+ // src/watcher/file-watcher.ts
3320
3382
  var FileWatcher = class {
3321
3383
  watcher = null;
3322
3384
  projectRoot;
@@ -3337,16 +3399,10 @@ var FileWatcher = class {
3337
3399
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3338
3400
  this.watcher = chokidar_default.watch(this.projectRoot, {
3339
3401
  ignored: (filePath) => {
3340
- const relativePath = path5.relative(this.projectRoot, filePath);
3402
+ const relativePath = path8.relative(this.projectRoot, filePath);
3341
3403
  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
- }
3404
+ if (hasFilteredPathSegment(relativePath, path8.sep)) {
3405
+ return true;
3350
3406
  }
3351
3407
  if (ignoreFilter.ignores(relativePath)) {
3352
3408
  return true;
@@ -3391,7 +3447,7 @@ var FileWatcher = class {
3391
3447
  return;
3392
3448
  }
3393
3449
  const changes = Array.from(this.pendingChanges.entries()).map(
3394
- ([path12, type]) => ({ path: path12, type })
3450
+ ([path18, type]) => ({ path: path18, type })
3395
3451
  );
3396
3452
  this.pendingChanges.clear();
3397
3453
  try {
@@ -3416,6 +3472,9 @@ var FileWatcher = class {
3416
3472
  return this.watcher !== null;
3417
3473
  }
3418
3474
  };
3475
+
3476
+ // src/watcher/git-head-watcher.ts
3477
+ import * as path9 from "path";
3419
3478
  var GitHeadWatcher = class {
3420
3479
  watcher = null;
3421
3480
  projectRoot;
@@ -3437,7 +3496,7 @@ var GitHeadWatcher = class {
3437
3496
  this.onBranchChange = handler;
3438
3497
  this.currentBranch = getCurrentBranch(this.projectRoot);
3439
3498
  const headPath = getHeadPath(this.projectRoot);
3440
- const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3499
+ const refsPath = path9.join(this.projectRoot, ".git", "refs", "heads");
3441
3500
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3442
3501
  persistent: true,
3443
3502
  ignoreInitial: true,
@@ -3489,6 +3548,8 @@ var GitHeadWatcher = class {
3489
3548
  return this.watcher !== null;
3490
3549
  }
3491
3550
  };
3551
+
3552
+ // src/watcher/index.ts
3492
3553
  function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3493
3554
  const fileWatcher = new FileWatcher(projectRoot, config);
3494
3555
  fileWatcher.start(async (changes) => {
@@ -3522,8 +3583,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3522
3583
  import { tool } from "@opencode-ai/plugin";
3523
3584
 
3524
3585
  // src/indexer/index.ts
3525
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
3526
- import * as path8 from "path";
3586
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
3587
+ import * as path12 from "path";
3527
3588
  import { performance as performance2 } from "perf_hooks";
3528
3589
 
3529
3590
  // node_modules/eventemitter3/index.mjs
@@ -3548,7 +3609,7 @@ function pTimeout(promise, options) {
3548
3609
  } = options;
3549
3610
  let timer;
3550
3611
  let abortHandler;
3551
- const wrappedPromise = new Promise((resolve9, reject) => {
3612
+ const wrappedPromise = new Promise((resolve11, reject) => {
3552
3613
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3553
3614
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3554
3615
  }
@@ -3562,7 +3623,7 @@ function pTimeout(promise, options) {
3562
3623
  };
3563
3624
  signal.addEventListener("abort", abortHandler, { once: true });
3564
3625
  }
3565
- promise.then(resolve9, reject);
3626
+ promise.then(resolve11, reject);
3566
3627
  if (milliseconds === Number.POSITIVE_INFINITY) {
3567
3628
  return;
3568
3629
  }
@@ -3570,7 +3631,7 @@ function pTimeout(promise, options) {
3570
3631
  timer = customTimers.setTimeout.call(void 0, () => {
3571
3632
  if (fallback) {
3572
3633
  try {
3573
- resolve9(fallback());
3634
+ resolve11(fallback());
3574
3635
  } catch (error) {
3575
3636
  reject(error);
3576
3637
  }
@@ -3580,7 +3641,7 @@ function pTimeout(promise, options) {
3580
3641
  promise.cancel();
3581
3642
  }
3582
3643
  if (message === false) {
3583
- resolve9();
3644
+ resolve11();
3584
3645
  } else if (message instanceof Error) {
3585
3646
  reject(message);
3586
3647
  } else {
@@ -3982,7 +4043,7 @@ var PQueue = class extends import_index.default {
3982
4043
  // Assign unique ID if not provided
3983
4044
  id: options.id ?? (this.#idAssigner++).toString()
3984
4045
  };
3985
- return new Promise((resolve9, reject) => {
4046
+ return new Promise((resolve11, reject) => {
3986
4047
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3987
4048
  let cleanupQueueAbortHandler = () => void 0;
3988
4049
  const run = async () => {
@@ -4022,7 +4083,7 @@ var PQueue = class extends import_index.default {
4022
4083
  })]);
4023
4084
  }
4024
4085
  const result = await operation;
4025
- resolve9(result);
4086
+ resolve11(result);
4026
4087
  this.emit("completed", result);
4027
4088
  } catch (error) {
4028
4089
  reject(error);
@@ -4210,13 +4271,13 @@ var PQueue = class extends import_index.default {
4210
4271
  });
4211
4272
  }
4212
4273
  async #onEvent(event, filter) {
4213
- return new Promise((resolve9) => {
4274
+ return new Promise((resolve11) => {
4214
4275
  const listener = () => {
4215
4276
  if (filter && !filter()) {
4216
4277
  return;
4217
4278
  }
4218
4279
  this.off(event, listener);
4219
- resolve9();
4280
+ resolve11();
4220
4281
  };
4221
4282
  this.on(event, listener);
4222
4283
  });
@@ -4502,7 +4563,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4502
4563
  const finalDelay = Math.min(delayTime, remainingTime);
4503
4564
  options.signal?.throwIfAborted();
4504
4565
  if (finalDelay > 0) {
4505
- await new Promise((resolve9, reject) => {
4566
+ await new Promise((resolve11, reject) => {
4506
4567
  const onAbort = () => {
4507
4568
  clearTimeout(timeoutToken);
4508
4569
  options.signal?.removeEventListener("abort", onAbort);
@@ -4510,7 +4571,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4510
4571
  };
4511
4572
  const timeoutToken = setTimeout(() => {
4512
4573
  options.signal?.removeEventListener("abort", onAbort);
4513
- resolve9();
4574
+ resolve11();
4514
4575
  }, finalDelay);
4515
4576
  if (options.unref) {
4516
4577
  timeoutToken.unref?.();
@@ -4571,17 +4632,17 @@ async function pRetry(input, options = {}) {
4571
4632
  }
4572
4633
 
4573
4634
  // src/embeddings/detector.ts
4574
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4575
- import * as path6 from "path";
4635
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4636
+ import * as path10 from "path";
4576
4637
  import * as os3 from "os";
4577
4638
  function getOpenCodeAuthPath() {
4578
- return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4639
+ return path10.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4579
4640
  }
4580
4641
  function loadOpenCodeAuth() {
4581
4642
  const authPath = getOpenCodeAuthPath();
4582
4643
  try {
4583
- if (existsSync5(authPath)) {
4584
- return JSON.parse(readFileSync4(authPath, "utf-8"));
4644
+ if (existsSync6(authPath)) {
4645
+ return JSON.parse(readFileSync5(authPath, "utf-8"));
4585
4646
  }
4586
4647
  } catch {
4587
4648
  }
@@ -4648,7 +4709,8 @@ function getGitHubCopilotCredentials() {
4648
4709
  if (!copilotAuth || copilotAuth.type !== "oauth") {
4649
4710
  return null;
4650
4711
  }
4651
- const baseUrl = copilotAuth.enterpriseUrl ? `https://copilot-api.${copilotAuth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4712
+ const auth = copilotAuth;
4713
+ const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4652
4714
  return {
4653
4715
  provider: "github-copilot",
4654
4716
  baseUrl,
@@ -4744,42 +4806,12 @@ function createCustomProviderInfo(config) {
4744
4806
  };
4745
4807
  }
4746
4808
 
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 {
4809
+ // src/embeddings/provider-types.ts
4810
+ var BaseEmbeddingProvider = class {
4773
4811
  constructor(credentials, modelInfo) {
4774
4812
  this.credentials = credentials;
4775
4813
  this.modelInfo = modelInfo;
4776
4814
  }
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
4815
  async embedQuery(query) {
4784
4816
  const result = await this.embedBatch([query]);
4785
4817
  return {
@@ -4794,69 +4826,204 @@ var GitHubCopilotEmbeddingProvider = class {
4794
4826
  tokensUsed: result.totalTokensUsed
4795
4827
  };
4796
4828
  }
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
4829
  getModelInfo() {
4823
4830
  return this.modelInfo;
4824
4831
  }
4825
4832
  };
4826
- var OpenAIEmbeddingProvider = class {
4833
+ var CustomProviderNonRetryableError = class extends Error {
4834
+ constructor(message) {
4835
+ super(message);
4836
+ this.name = "CustomProviderNonRetryableError";
4837
+ }
4838
+ };
4839
+
4840
+ // src/utils/url-validation.ts
4841
+ var BLOCKED_METADATA_IPS = [
4842
+ /^169\.254\.169\.254$/,
4843
+ // AWS/Azure/GCP metadata
4844
+ /^169\.254\.170\.2$/,
4845
+ // AWS ECS task metadata
4846
+ /^fd00:ec2::254$/
4847
+ // AWS IMDSv2 IPv6
4848
+ ];
4849
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
4850
+ "metadata.google.internal",
4851
+ "metadata.google",
4852
+ "metadata.goog",
4853
+ "kubernetes.default.svc"
4854
+ ]);
4855
+ function validateExternalUrl(urlString) {
4856
+ let parsed;
4857
+ try {
4858
+ parsed = new URL(urlString);
4859
+ } catch {
4860
+ return { valid: false, reason: `Invalid URL: ${sanitizeUrlForError(urlString)}` };
4861
+ }
4862
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4863
+ return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
4864
+ }
4865
+ const hostname = parsed.hostname.toLowerCase();
4866
+ if (BLOCKED_HOSTNAMES.has(hostname)) {
4867
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
4868
+ }
4869
+ for (const pattern of BLOCKED_METADATA_IPS) {
4870
+ if (pattern.test(hostname)) {
4871
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
4872
+ }
4873
+ }
4874
+ if (/^169\.254\./.test(hostname)) {
4875
+ return { valid: false, reason: `Blocked: link-local address (${hostname})` };
4876
+ }
4877
+ return { valid: true };
4878
+ }
4879
+ function sanitizeUrlForError(url) {
4880
+ try {
4881
+ const parsed = new URL(url);
4882
+ parsed.username = "";
4883
+ parsed.password = "";
4884
+ return parsed.toString();
4885
+ } catch {
4886
+ const maxLen = 80;
4887
+ if (url.length > maxLen) {
4888
+ return url.slice(0, maxLen) + "...";
4889
+ }
4890
+ return url;
4891
+ }
4892
+ }
4893
+
4894
+ // src/embeddings/providers/custom.ts
4895
+ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
4827
4896
  constructor(credentials, modelInfo) {
4828
- this.credentials = credentials;
4829
- this.modelInfo = modelInfo;
4897
+ super(credentials, modelInfo);
4830
4898
  }
4831
- async embedQuery(query) {
4832
- const result = await this.embedBatch([query]);
4833
- return {
4834
- embedding: result.embeddings[0],
4835
- tokensUsed: result.totalTokensUsed
4899
+ splitIntoRequestBatches(texts) {
4900
+ const maxBatchSize = this.modelInfo.maxBatchSize;
4901
+ if (!maxBatchSize || texts.length <= maxBatchSize) {
4902
+ return [texts];
4903
+ }
4904
+ const batches = [];
4905
+ for (let i = 0; i < texts.length; i += maxBatchSize) {
4906
+ batches.push(texts.slice(i, i + maxBatchSize));
4907
+ }
4908
+ return batches;
4909
+ }
4910
+ async embedRequest(texts) {
4911
+ if (texts.length === 0) {
4912
+ return {
4913
+ embeddings: [],
4914
+ totalTokensUsed: 0
4915
+ };
4916
+ }
4917
+ const headers = {
4918
+ "Content-Type": "application/json"
4836
4919
  };
4920
+ if (this.credentials.apiKey) {
4921
+ headers.Authorization = `Bearer ${this.credentials.apiKey}`;
4922
+ }
4923
+ const baseUrl = this.credentials.baseUrl ?? "";
4924
+ const fullUrl = `${baseUrl}/embeddings`;
4925
+ const urlCheck = validateExternalUrl(fullUrl);
4926
+ if (!urlCheck.valid) {
4927
+ throw new CustomProviderNonRetryableError(
4928
+ `Custom embedding provider URL blocked (SSRF protection): ${urlCheck.reason}`
4929
+ );
4930
+ }
4931
+ const timeoutMs = this.modelInfo.timeoutMs;
4932
+ const controller = new AbortController();
4933
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
4934
+ let response;
4935
+ try {
4936
+ response = await fetch(fullUrl, {
4937
+ method: "POST",
4938
+ headers,
4939
+ body: JSON.stringify({
4940
+ model: this.modelInfo.model,
4941
+ input: texts
4942
+ }),
4943
+ signal: controller.signal
4944
+ });
4945
+ } catch (error) {
4946
+ if (error instanceof Error && error.name === "AbortError") {
4947
+ throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${sanitizeUrlForError(fullUrl)}`);
4948
+ }
4949
+ throw error;
4950
+ } finally {
4951
+ clearTimeout(timeout);
4952
+ }
4953
+ if (!response.ok) {
4954
+ const errorText = (await response.text()).slice(0, 500);
4955
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4956
+ throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
4957
+ }
4958
+ throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
4959
+ }
4960
+ const data = await response.json();
4961
+ if (data.data && Array.isArray(data.data)) {
4962
+ if (data.data.length > 0) {
4963
+ const actualDims = data.data[0].embedding.length;
4964
+ if (actualDims !== this.modelInfo.dimensions) {
4965
+ throw new Error(
4966
+ `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.`
4967
+ );
4968
+ }
4969
+ }
4970
+ if (data.data.length !== texts.length) {
4971
+ throw new Error(
4972
+ `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
4973
+ );
4974
+ }
4975
+ return {
4976
+ embeddings: data.data.map((d) => d.embedding),
4977
+ totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
4978
+ };
4979
+ }
4980
+ throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
4837
4981
  }
4838
- async embedDocument(document) {
4839
- const result = await this.embedBatch([document]);
4982
+ async embedBatch(texts) {
4983
+ const requestBatches = this.splitIntoRequestBatches(texts);
4984
+ const embeddings = [];
4985
+ let totalTokensUsed = 0;
4986
+ for (const batch of requestBatches) {
4987
+ const result = await this.embedRequest(batch);
4988
+ embeddings.push(...result.embeddings);
4989
+ totalTokensUsed += result.totalTokensUsed;
4990
+ }
4840
4991
  return {
4841
- embedding: result.embeddings[0],
4842
- tokensUsed: result.totalTokensUsed
4992
+ embeddings,
4993
+ totalTokensUsed
4843
4994
  };
4844
4995
  }
4996
+ };
4997
+
4998
+ // src/embeddings/providers/github-copilot.ts
4999
+ var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
5000
+ constructor(credentials, modelInfo) {
5001
+ super(credentials, modelInfo);
5002
+ }
5003
+ getToken() {
5004
+ if (!this.credentials.refreshToken) {
5005
+ throw new Error("No OAuth token available for GitHub");
5006
+ }
5007
+ return this.credentials.refreshToken;
5008
+ }
4845
5009
  async embedBatch(texts) {
4846
- const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5010
+ const token = this.getToken();
5011
+ const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4847
5012
  method: "POST",
4848
5013
  headers: {
4849
- Authorization: `Bearer ${this.credentials.apiKey}`,
4850
- "Content-Type": "application/json"
5014
+ Authorization: `Bearer ${token}`,
5015
+ "Content-Type": "application/json",
5016
+ Accept: "application/vnd.github+json",
5017
+ "X-GitHub-Api-Version": "2022-11-28"
4851
5018
  },
4852
5019
  body: JSON.stringify({
4853
- model: this.modelInfo.model,
5020
+ model: `openai/${this.modelInfo.model}`,
4854
5021
  input: texts
4855
5022
  })
4856
5023
  });
4857
5024
  if (!response.ok) {
4858
- const error = await response.text();
4859
- throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5025
+ const error = (await response.text()).slice(0, 500);
5026
+ throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4860
5027
  }
4861
5028
  const data = await response.json();
4862
5029
  return {
@@ -4864,16 +5031,14 @@ var OpenAIEmbeddingProvider = class {
4864
5031
  totalTokensUsed: data.usage.total_tokens
4865
5032
  };
4866
5033
  }
4867
- getModelInfo() {
4868
- return this.modelInfo;
4869
- }
4870
5034
  };
4871
- var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
5035
+
5036
+ // src/embeddings/providers/google.ts
5037
+ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
5038
+ static BATCH_SIZE = 20;
4872
5039
  constructor(credentials, modelInfo) {
4873
- this.credentials = credentials;
4874
- this.modelInfo = modelInfo;
5040
+ super(credentials, modelInfo);
4875
5041
  }
4876
- static BATCH_SIZE = 20;
4877
5042
  async embedQuery(query) {
4878
5043
  const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
4879
5044
  const result = await this.embedWithTaskType([query], taskType);
@@ -4894,12 +5059,6 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4894
5059
  const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
4895
5060
  return this.embedWithTaskType(texts, taskType);
4896
5061
  }
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
5062
  async embedWithTaskType(texts, taskType) {
4904
5063
  const batches = [];
4905
5064
  for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
@@ -4916,17 +5075,18 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4916
5075
  outputDimensionality: this.modelInfo.dimensions
4917
5076
  }));
4918
5077
  const response = await fetch(
4919
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents?key=${this.credentials.apiKey}`,
5078
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents`,
4920
5079
  {
4921
5080
  method: "POST",
4922
5081
  headers: {
4923
- "Content-Type": "application/json"
5082
+ "Content-Type": "application/json",
5083
+ ...this.credentials.apiKey && { "x-goog-api-key": this.credentials.apiKey }
4924
5084
  },
4925
5085
  body: JSON.stringify({ requests })
4926
5086
  }
4927
5087
  );
4928
5088
  if (!response.ok) {
4929
- const error = await response.text();
5089
+ const error = (await response.text()).slice(0, 500);
4930
5090
  throw new Error(`Google embedding API error: ${response.status} - ${error}`);
4931
5091
  }
4932
5092
  const data = await response.json();
@@ -4941,29 +5101,13 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4941
5101
  totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
4942
5102
  };
4943
5103
  }
4944
- getModelInfo() {
4945
- return this.modelInfo;
4946
- }
4947
5104
  };
4948
- var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4949
- constructor(credentials, modelInfo) {
4950
- this.credentials = credentials;
4951
- this.modelInfo = modelInfo;
4952
- }
5105
+
5106
+ // src/embeddings/providers/ollama.ts
5107
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
4953
5108
  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
- };
5109
+ constructor(credentials, modelInfo) {
5110
+ super(credentials, modelInfo);
4967
5111
  }
4968
5112
  estimateTokens(text) {
4969
5113
  return Math.ceil(text.length / 4);
@@ -5026,167 +5170,98 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
5026
5170
  try {
5027
5171
  return await this.embedSingle(truncated);
5028
5172
  } catch (retryError) {
5029
- 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
- );
5173
+ if (!this.isContextLengthError(retryError)) {
5174
+ throw retryError;
5175
+ }
5176
+ lastError = retryError;
5141
5177
  }
5142
5178
  }
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
- };
5179
+ throw lastError;
5154
5180
  }
5155
- throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
5156
5181
  }
5157
- async embedQuery(query) {
5158
- const result = await this.embedBatch([query]);
5182
+ async embedSingle(text) {
5183
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5184
+ method: "POST",
5185
+ headers: {
5186
+ "Content-Type": "application/json"
5187
+ },
5188
+ body: JSON.stringify({
5189
+ model: this.modelInfo.model,
5190
+ prompt: text,
5191
+ truncate: false
5192
+ })
5193
+ });
5194
+ if (!response.ok) {
5195
+ const error = (await response.text()).slice(0, 500);
5196
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5197
+ }
5198
+ const data = await response.json();
5159
5199
  return {
5160
- embedding: result.embeddings[0],
5161
- tokensUsed: result.totalTokensUsed
5200
+ embedding: data.embedding,
5201
+ tokensUsed: this.estimateTokens(text)
5162
5202
  };
5163
5203
  }
5164
- async embedDocument(document) {
5165
- const result = await this.embedBatch([document]);
5204
+ async embedBatch(texts) {
5205
+ const results = [];
5206
+ for (const text of texts) {
5207
+ results.push(await this.embedSingleWithFallback(text));
5208
+ }
5166
5209
  return {
5167
- embedding: result.embeddings[0],
5168
- tokensUsed: result.totalTokensUsed
5210
+ embeddings: results.map((r) => r.embedding),
5211
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5169
5212
  };
5170
5213
  }
5214
+ };
5215
+
5216
+ // src/embeddings/providers/openai.ts
5217
+ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
5218
+ constructor(credentials, modelInfo) {
5219
+ super(credentials, modelInfo);
5220
+ }
5171
5221
  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;
5222
+ const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5223
+ method: "POST",
5224
+ headers: {
5225
+ Authorization: `Bearer ${this.credentials.apiKey}`,
5226
+ "Content-Type": "application/json"
5227
+ },
5228
+ body: JSON.stringify({
5229
+ model: this.modelInfo.model,
5230
+ input: texts
5231
+ })
5232
+ });
5233
+ if (!response.ok) {
5234
+ const error = (await response.text()).slice(0, 500);
5235
+ throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5179
5236
  }
5237
+ const data = await response.json();
5180
5238
  return {
5181
- embeddings,
5182
- totalTokensUsed
5239
+ embeddings: data.data.map((d) => d.embedding),
5240
+ totalTokensUsed: data.usage.total_tokens
5183
5241
  };
5184
5242
  }
5185
- getModelInfo() {
5186
- return this.modelInfo;
5187
- }
5188
5243
  };
5189
5244
 
5245
+ // src/embeddings/provider.ts
5246
+ function createEmbeddingProvider(configuredProviderInfo) {
5247
+ switch (configuredProviderInfo.provider) {
5248
+ case "github-copilot":
5249
+ return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5250
+ case "openai":
5251
+ return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5252
+ case "google":
5253
+ return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5254
+ case "ollama":
5255
+ return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5256
+ case "custom":
5257
+ return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5258
+ default: {
5259
+ const _exhaustive = configuredProviderInfo;
5260
+ throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
5261
+ }
5262
+ }
5263
+ }
5264
+
5190
5265
  // src/rerank/index.ts
5191
5266
  function createReranker(config) {
5192
5267
  if (!config.enabled) {
@@ -5222,7 +5297,10 @@ var SiliconFlowReranker = class {
5222
5297
  if (this.config.apiKey) {
5223
5298
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
5224
5299
  }
5225
- const baseUrl = this.config.baseUrl ?? "https://api.siliconflow.cn/v1";
5300
+ const baseUrl = this.config.baseUrl;
5301
+ if (!baseUrl) {
5302
+ throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
5303
+ }
5226
5304
  const timeoutMs = this.config.timeoutMs ?? 3e4;
5227
5305
  const controller = new AbortController();
5228
5306
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -5296,20 +5374,21 @@ function createCostEstimate(files, provider) {
5296
5374
  }
5297
5375
  function formatCostEstimate(estimate) {
5298
5376
  const sizeFormatted = formatBytes(estimate.totalSizeBytes);
5377
+ const filesFormatted = `${estimate.filesCount.toLocaleString()} files`;
5299
5378
  const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
5300
5379
  return `
5301
5380
  \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
5381
  \u2502 \u{1F4CA} Indexing Estimate \u2502
5303
5382
  \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
5383
  \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
5384
+ \u2502 Files to index: ${filesFormatted.padEnd(40)}\u2502
5385
+ \u2502 Total size: ${sizeFormatted.padEnd(40)}\u2502
5386
+ \u2502 Estimated chunks: ${("~" + estimate.estimatedChunks.toLocaleString() + " chunks").padEnd(40)}\u2502
5387
+ \u2502 Estimated tokens: ${("~" + estimate.estimatedTokens.toLocaleString() + " tokens").padEnd(40)}\u2502
5309
5388
  \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
5389
+ \u2502 Provider: ${estimate.provider.padEnd(52)}\u2502
5390
+ \u2502 Model: ${estimate.model.padEnd(52)}\u2502
5391
+ \u2502 Cost: ${costFormatted.padEnd(52)}\u2502
5313
5392
  \u2502 \u2502
5314
5393
  \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
5394
  `;
@@ -5321,9 +5400,6 @@ function formatBytes(bytes) {
5321
5400
  const i = Math.floor(Math.log(bytes) / Math.log(k));
5322
5401
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
5323
5402
  }
5324
- function padRight(str, length) {
5325
- return str.padEnd(length);
5326
- }
5327
5403
 
5328
5404
  // src/utils/logger.ts
5329
5405
  var LOG_LEVEL_PRIORITY = {
@@ -5390,6 +5466,10 @@ var Logger = class {
5390
5466
  this.logs.shift();
5391
5467
  }
5392
5468
  }
5469
+ withMetrics(fn) {
5470
+ if (!this.config.metrics) return;
5471
+ fn();
5472
+ }
5393
5473
  search(level, message, data) {
5394
5474
  if (this.config.logSearch) {
5395
5475
  this.log(level, "search", message, data);
@@ -5428,89 +5508,107 @@ var Logger = class {
5428
5508
  this.log("debug", "general", message, data);
5429
5509
  }
5430
5510
  recordIndexingStart() {
5431
- if (!this.config.metrics) return;
5432
- this.metrics.indexingStartTime = Date.now();
5511
+ this.withMetrics(() => {
5512
+ this.metrics.indexingStartTime = Date.now();
5513
+ });
5433
5514
  }
5434
5515
  recordIndexingEnd() {
5435
- if (!this.config.metrics) return;
5436
- this.metrics.indexingEndTime = Date.now();
5516
+ this.withMetrics(() => {
5517
+ this.metrics.indexingEndTime = Date.now();
5518
+ });
5437
5519
  }
5438
5520
  recordFilesScanned(count) {
5439
- if (!this.config.metrics) return;
5440
- this.metrics.filesScanned = count;
5521
+ this.withMetrics(() => {
5522
+ this.metrics.filesScanned = count;
5523
+ });
5441
5524
  }
5442
5525
  recordFilesParsed(count) {
5443
- if (!this.config.metrics) return;
5444
- this.metrics.filesParsed = count;
5526
+ this.withMetrics(() => {
5527
+ this.metrics.filesParsed = count;
5528
+ });
5445
5529
  }
5446
5530
  recordParseDuration(durationMs) {
5447
- if (!this.config.metrics) return;
5448
- this.metrics.parseMs = durationMs;
5531
+ this.withMetrics(() => {
5532
+ this.metrics.parseMs = durationMs;
5533
+ });
5449
5534
  }
5450
5535
  recordChunksProcessed(count) {
5451
- if (!this.config.metrics) return;
5452
- this.metrics.chunksProcessed += count;
5536
+ this.withMetrics(() => {
5537
+ this.metrics.chunksProcessed += count;
5538
+ });
5453
5539
  }
5454
5540
  recordChunksEmbedded(count) {
5455
- if (!this.config.metrics) return;
5456
- this.metrics.chunksEmbedded += count;
5541
+ this.withMetrics(() => {
5542
+ this.metrics.chunksEmbedded += count;
5543
+ });
5457
5544
  }
5458
5545
  recordChunksFromCache(count) {
5459
- if (!this.config.metrics) return;
5460
- this.metrics.chunksFromCache += count;
5546
+ this.withMetrics(() => {
5547
+ this.metrics.chunksFromCache += count;
5548
+ });
5461
5549
  }
5462
5550
  recordChunksRemoved(count) {
5463
- if (!this.config.metrics) return;
5464
- this.metrics.chunksRemoved += count;
5551
+ this.withMetrics(() => {
5552
+ this.metrics.chunksRemoved += count;
5553
+ });
5465
5554
  }
5466
5555
  recordEmbeddingApiCall(tokens) {
5467
- if (!this.config.metrics) return;
5468
- this.metrics.embeddingApiCalls++;
5469
- this.metrics.embeddingTokensUsed += tokens;
5556
+ this.withMetrics(() => {
5557
+ this.metrics.embeddingApiCalls++;
5558
+ this.metrics.embeddingTokensUsed += tokens;
5559
+ });
5470
5560
  }
5471
5561
  recordEmbeddingError() {
5472
- if (!this.config.metrics) return;
5473
- this.metrics.embeddingErrors++;
5562
+ this.withMetrics(() => {
5563
+ this.metrics.embeddingErrors++;
5564
+ });
5474
5565
  }
5475
5566
  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
- }
5567
+ this.withMetrics(() => {
5568
+ this.metrics.searchCount++;
5569
+ this.metrics.searchTotalMs += durationMs;
5570
+ this.metrics.searchLastMs = durationMs;
5571
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5572
+ if (breakdown) {
5573
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
5574
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
5575
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
5576
+ this.metrics.fusionMs = breakdown.fusionMs;
5577
+ }
5578
+ });
5487
5579
  }
5488
5580
  recordCacheHit() {
5489
- if (!this.config.metrics) return;
5490
- this.metrics.cacheHits++;
5581
+ this.withMetrics(() => {
5582
+ this.metrics.cacheHits++;
5583
+ });
5491
5584
  }
5492
5585
  recordCacheMiss() {
5493
- if (!this.config.metrics) return;
5494
- this.metrics.cacheMisses++;
5586
+ this.withMetrics(() => {
5587
+ this.metrics.cacheMisses++;
5588
+ });
5495
5589
  }
5496
5590
  recordQueryCacheHit() {
5497
- if (!this.config.metrics) return;
5498
- this.metrics.queryCacheHits++;
5591
+ this.withMetrics(() => {
5592
+ this.metrics.queryCacheHits++;
5593
+ });
5499
5594
  }
5500
5595
  recordQueryCacheSimilarHit() {
5501
- if (!this.config.metrics) return;
5502
- this.metrics.queryCacheSimilarHits++;
5596
+ this.withMetrics(() => {
5597
+ this.metrics.queryCacheSimilarHits++;
5598
+ });
5503
5599
  }
5504
5600
  recordQueryCacheMiss() {
5505
- if (!this.config.metrics) return;
5506
- this.metrics.queryCacheMisses++;
5601
+ this.withMetrics(() => {
5602
+ this.metrics.queryCacheMisses++;
5603
+ });
5507
5604
  }
5508
5605
  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;
5606
+ this.withMetrics(() => {
5607
+ this.metrics.gcRuns++;
5608
+ this.metrics.gcOrphansRemoved += orphans;
5609
+ this.metrics.gcChunksRemoved += chunks;
5610
+ this.metrics.gcEmbeddingsRemoved += embeddings;
5611
+ });
5514
5612
  }
5515
5613
  getMetrics() {
5516
5614
  return { ...this.metrics };
@@ -5617,7 +5715,7 @@ function initializeLogger(config) {
5617
5715
  }
5618
5716
 
5619
5717
  // src/native/index.ts
5620
- import * as path7 from "path";
5718
+ import * as path11 from "path";
5621
5719
  import * as os4 from "os";
5622
5720
  import * as module from "module";
5623
5721
  import { fileURLToPath } from "url";
@@ -5641,19 +5739,19 @@ function getNativeBinding() {
5641
5739
  let currentDir;
5642
5740
  let requireTarget;
5643
5741
  if (typeof import.meta !== "undefined" && import.meta.url) {
5644
- currentDir = path7.dirname(fileURLToPath(import.meta.url));
5742
+ currentDir = path11.dirname(fileURLToPath(import.meta.url));
5645
5743
  requireTarget = import.meta.url;
5646
5744
  } else if (typeof __dirname !== "undefined") {
5647
5745
  currentDir = __dirname;
5648
5746
  requireTarget = __filename;
5649
5747
  } else {
5650
5748
  currentDir = process.cwd();
5651
- requireTarget = path7.join(currentDir, "index.js");
5749
+ requireTarget = path11.join(currentDir, "index.js");
5652
5750
  }
5653
5751
  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);
5752
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path11.join("src", "native"));
5753
+ const packageRoot = isDevMode ? path11.resolve(currentDir, "../..") : path11.resolve(currentDir, "..");
5754
+ const nativePath = path11.join(packageRoot, "native", bindingName);
5657
5755
  const require2 = module.createRequire(requireTarget);
5658
5756
  return require2(nativePath);
5659
5757
  }
@@ -6223,7 +6321,6 @@ var Database = class {
6223
6321
  this.throwIfClosed();
6224
6322
  return this.inner.getStats();
6225
6323
  }
6226
- // ── Symbol methods ──────────────────────────────────────────────
6227
6324
  upsertSymbol(symbol) {
6228
6325
  this.throwIfClosed();
6229
6326
  this.inner.upsertSymbol(symbol);
@@ -6253,7 +6350,6 @@ var Database = class {
6253
6350
  this.throwIfClosed();
6254
6351
  return this.inner.deleteSymbolsByFile(filePath);
6255
6352
  }
6256
- // ── Call Edge methods ────────────────────────────────────────────
6257
6353
  upsertCallEdge(edge) {
6258
6354
  this.throwIfClosed();
6259
6355
  this.inner.upsertCallEdge(edge);
@@ -6283,7 +6379,6 @@ var Database = class {
6283
6379
  this.throwIfClosed();
6284
6380
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6285
6381
  }
6286
- // ── Branch Symbol methods ────────────────────────────────────────
6287
6382
  addSymbolsToBranch(branch, symbolIds) {
6288
6383
  this.throwIfClosed();
6289
6384
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6316,7 +6411,6 @@ var Database = class {
6316
6411
  if (symbolIds.length === 0) return 0;
6317
6412
  return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6318
6413
  }
6319
- // ── GC methods for symbols/edges ─────────────────────────────────
6320
6414
  gcOrphanSymbols() {
6321
6415
  this.throwIfClosed();
6322
6416
  return this.inner.gcOrphanSymbols();
@@ -6328,7 +6422,7 @@ var Database = class {
6328
6422
  };
6329
6423
 
6330
6424
  // src/indexer/index.ts
6331
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6425
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6332
6426
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
6333
6427
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6334
6428
  "function_declaration",
@@ -6355,7 +6449,15 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6355
6449
  "trigger_declaration",
6356
6450
  "test_declaration",
6357
6451
  "struct_declaration",
6358
- "union_declaration"
6452
+ "union_declaration",
6453
+ // GDScript declarations whose names participate in the call graph.
6454
+ // `function_definition` and `class_definition` are already in the set
6455
+ // above (shared with Python/C/Bash and Python, respectively).
6456
+ "constructor_definition",
6457
+ "enum_definition",
6458
+ "signal_statement",
6459
+ "const_statement",
6460
+ "class_name_statement"
6359
6461
  ]);
6360
6462
  function float32ArrayToBuffer(arr) {
6361
6463
  const float32 = new Float32Array(arr);
@@ -6557,9 +6659,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6557
6659
  return true;
6558
6660
  }
6559
6661
  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}`);
6662
+ const normalizedFilePath = path12.resolve(filePath);
6663
+ const normalizedRoot = path12.resolve(rootPath);
6664
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6563
6665
  }
6564
6666
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6565
6667
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7612,22 +7714,28 @@ var Indexer = class {
7612
7714
  this.projectRoot = projectRoot;
7613
7715
  this.config = config;
7614
7716
  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");
7717
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7718
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7719
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7618
7720
  this.logger = initializeLogger(config.debug);
7619
7721
  }
7620
7722
  getIndexPath() {
7621
7723
  return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7622
7724
  }
7623
7725
  loadFileHashCache() {
7726
+ if (!existsSync7(this.fileHashCachePath)) {
7727
+ return;
7728
+ }
7624
7729
  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 {
7730
+ const data = readFileSync6(this.fileHashCachePath, "utf-8");
7731
+ const parsed = JSON.parse(data);
7732
+ this.fileHashCache = new Map(Object.entries(parsed));
7733
+ } catch (error) {
7734
+ const message = error instanceof Error ? error.message : String(error);
7735
+ this.logger.warn("Failed to load file hash cache, resetting cache state", {
7736
+ fileHashCachePath: this.fileHashCachePath,
7737
+ error: message
7738
+ });
7631
7739
  this.fileHashCache = /* @__PURE__ */ new Map();
7632
7740
  }
7633
7741
  }
@@ -7640,13 +7748,14 @@ var Indexer = class {
7640
7748
  }
7641
7749
  atomicWriteSync(targetPath, data) {
7642
7750
  const tempPath = `${targetPath}.tmp`;
7751
+ mkdirSync2(path12.dirname(targetPath), { recursive: true });
7643
7752
  writeFileSync2(tempPath, data);
7644
7753
  renameSync(tempPath, targetPath);
7645
7754
  }
7646
7755
  getScopedRoots() {
7647
- const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7756
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7648
7757
  for (const kbRoot of this.config.knowledgeBases) {
7649
- roots.add(path8.resolve(this.projectRoot, kbRoot));
7758
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
7650
7759
  }
7651
7760
  return Array.from(roots);
7652
7761
  }
@@ -7655,22 +7764,22 @@ var Indexer = class {
7655
7764
  if (this.config.scope !== "global") {
7656
7765
  return branchName;
7657
7766
  }
7658
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7767
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7659
7768
  return `${projectHash}:${branchName}`;
7660
7769
  }
7661
7770
  getLegacyBranchCatalogKey() {
7662
7771
  return this.currentBranch || "default";
7663
7772
  }
7664
7773
  getLegacyMigrationMetadataKey() {
7665
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7774
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7666
7775
  return `index.globalBranchMigration.${projectHash}`;
7667
7776
  }
7668
7777
  getProjectEmbeddingStrategyMetadataKey() {
7669
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7778
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7670
7779
  return `index.embeddingStrategyVersion.${projectHash}`;
7671
7780
  }
7672
7781
  getProjectForceReembedMetadataKey() {
7673
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7782
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7674
7783
  return `index.forceReembed.${projectHash}`;
7675
7784
  }
7676
7785
  hasProjectForceReembedPending() {
@@ -7764,7 +7873,7 @@ var Indexer = class {
7764
7873
  if (!this.database) {
7765
7874
  return { chunkIds, symbolIds };
7766
7875
  }
7767
- const projectRootPath = path8.resolve(this.projectRoot);
7876
+ const projectRootPath = path12.resolve(this.projectRoot);
7768
7877
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7769
7878
  ...Array.from(this.fileHashCache.keys()).filter(
7770
7879
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7787,7 +7896,7 @@ var Indexer = class {
7787
7896
  if (this.config.scope !== "global") {
7788
7897
  return this.getBranchCatalogCleanupKeys();
7789
7898
  }
7790
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7899
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7791
7900
  const keys = /* @__PURE__ */ new Set();
7792
7901
  const projectChunkIdSet = new Set(projectChunkIds);
7793
7902
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7871,7 +7980,7 @@ var Indexer = class {
7871
7980
  if (!this.database || this.config.scope !== "global") {
7872
7981
  return false;
7873
7982
  }
7874
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7983
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7875
7984
  const roots = this.getScopedRoots();
7876
7985
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7877
7986
  return this.database.getAllBranches().some(
@@ -7905,7 +8014,7 @@ var Indexer = class {
7905
8014
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7906
8015
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7907
8016
  ]);
7908
- const projectRootPath = path8.resolve(this.projectRoot);
8017
+ const projectRootPath = path12.resolve(this.projectRoot);
7909
8018
  const projectLocalFilePaths = new Set(
7910
8019
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7911
8020
  );
@@ -7974,7 +8083,7 @@ var Indexer = class {
7974
8083
  };
7975
8084
  }
7976
8085
  checkForInterruptedIndexing() {
7977
- return existsSync6(this.indexingLockPath);
8086
+ return existsSync7(this.indexingLockPath);
7978
8087
  }
7979
8088
  acquireIndexingLock() {
7980
8089
  const lockData = {
@@ -7984,13 +8093,13 @@ var Indexer = class {
7984
8093
  writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
7985
8094
  }
7986
8095
  releaseIndexingLock() {
7987
- if (existsSync6(this.indexingLockPath)) {
8096
+ if (existsSync7(this.indexingLockPath)) {
7988
8097
  unlinkSync(this.indexingLockPath);
7989
8098
  }
7990
8099
  }
7991
8100
  async recoverFromInterruptedIndexing() {
7992
8101
  this.logger.warn("Detected interrupted indexing session, recovering...");
7993
- if (existsSync6(this.fileHashCachePath)) {
8102
+ if (existsSync7(this.fileHashCachePath)) {
7994
8103
  unlinkSync(this.fileHashCachePath);
7995
8104
  }
7996
8105
  await this.healthCheck();
@@ -8000,15 +8109,20 @@ var Indexer = class {
8000
8109
  loadFailedBatches(maxChunkTokens) {
8001
8110
  try {
8002
8111
  return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8003
- } catch {
8112
+ } catch (error) {
8113
+ const message = error instanceof Error ? error.message : String(error);
8114
+ this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
8115
+ failedBatchesPath: this.failedBatchesPath,
8116
+ error: message
8117
+ });
8004
8118
  return [];
8005
8119
  }
8006
8120
  }
8007
8121
  loadSerializedFailedBatches() {
8008
- if (!existsSync6(this.failedBatchesPath)) {
8122
+ if (!existsSync7(this.failedBatchesPath)) {
8009
8123
  return [];
8010
8124
  }
8011
- const data = readFileSync5(this.failedBatchesPath, "utf-8");
8125
+ const data = readFileSync6(this.failedBatchesPath, "utf-8");
8012
8126
  const parsed = JSON.parse(data);
8013
8127
  return parsed.map((batch) => {
8014
8128
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -8025,7 +8139,7 @@ var Indexer = class {
8025
8139
  }
8026
8140
  saveFailedBatches(batches) {
8027
8141
  if (batches.length === 0) {
8028
- if (existsSync6(this.failedBatchesPath)) {
8142
+ if (existsSync7(this.failedBatchesPath)) {
8029
8143
  try {
8030
8144
  unlinkSync(this.failedBatchesPath);
8031
8145
  } catch {
@@ -8260,24 +8374,24 @@ var Indexer = class {
8260
8374
  }
8261
8375
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
8262
8376
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8263
- const storePath = path8.join(this.indexPath, "vectors");
8377
+ const storePath = path12.join(this.indexPath, "vectors");
8264
8378
  this.store = new VectorStore(storePath, dimensions);
8265
- const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8266
- if (existsSync6(indexFilePath)) {
8379
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8380
+ if (existsSync7(indexFilePath)) {
8267
8381
  this.store.load();
8268
8382
  }
8269
- const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
8383
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8270
8384
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8271
8385
  try {
8272
8386
  this.invertedIndex.load();
8273
8387
  } catch {
8274
- if (existsSync6(invertedIndexPath)) {
8388
+ if (existsSync7(invertedIndexPath)) {
8275
8389
  await fsPromises2.unlink(invertedIndexPath);
8276
8390
  }
8277
8391
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8278
8392
  }
8279
- const dbPath = path8.join(this.indexPath, "codebase.db");
8280
- let dbIsNew = !existsSync6(dbPath);
8393
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8394
+ let dbIsNew = !existsSync7(dbPath);
8281
8395
  try {
8282
8396
  this.database = new Database(dbPath);
8283
8397
  } catch (error) {
@@ -8357,7 +8471,7 @@ var Indexer = class {
8357
8471
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8358
8472
  return {
8359
8473
  resetCorruptedIndex: true,
8360
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8474
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8361
8475
  };
8362
8476
  }
8363
8477
  throw error;
@@ -8372,7 +8486,7 @@ var Indexer = class {
8372
8486
  return;
8373
8487
  }
8374
8488
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8375
- const storeBasePath = path8.join(this.indexPath, "vectors");
8489
+ const storeBasePath = path12.join(this.indexPath, "vectors");
8376
8490
  const storeIndexPath = `${storeBasePath}.usearch`;
8377
8491
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8378
8492
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8381,18 +8495,18 @@ var Indexer = class {
8381
8495
  let backedUpMetadata = false;
8382
8496
  let rebuiltCount = 0;
8383
8497
  let skippedCount = 0;
8384
- if (existsSync6(backupIndexPath)) {
8498
+ if (existsSync7(backupIndexPath)) {
8385
8499
  unlinkSync(backupIndexPath);
8386
8500
  }
8387
- if (existsSync6(backupMetadataPath)) {
8501
+ if (existsSync7(backupMetadataPath)) {
8388
8502
  unlinkSync(backupMetadataPath);
8389
8503
  }
8390
8504
  try {
8391
- if (existsSync6(storeIndexPath)) {
8505
+ if (existsSync7(storeIndexPath)) {
8392
8506
  renameSync(storeIndexPath, backupIndexPath);
8393
8507
  backedUpIndex = true;
8394
8508
  }
8395
- if (existsSync6(storeMetadataPath)) {
8509
+ if (existsSync7(storeMetadataPath)) {
8396
8510
  renameSync(storeMetadataPath, backupMetadataPath);
8397
8511
  backedUpMetadata = true;
8398
8512
  }
@@ -8413,10 +8527,10 @@ var Indexer = class {
8413
8527
  rebuiltCount += 1;
8414
8528
  }
8415
8529
  store.save();
8416
- if (backedUpIndex && existsSync6(backupIndexPath)) {
8530
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
8417
8531
  unlinkSync(backupIndexPath);
8418
8532
  }
8419
- if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8533
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
8420
8534
  unlinkSync(backupMetadataPath);
8421
8535
  }
8422
8536
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -8429,16 +8543,16 @@ var Indexer = class {
8429
8543
  store.clear();
8430
8544
  } catch {
8431
8545
  }
8432
- if (existsSync6(storeIndexPath)) {
8546
+ if (existsSync7(storeIndexPath)) {
8433
8547
  unlinkSync(storeIndexPath);
8434
8548
  }
8435
- if (existsSync6(storeMetadataPath)) {
8549
+ if (existsSync7(storeMetadataPath)) {
8436
8550
  unlinkSync(storeMetadataPath);
8437
8551
  }
8438
- if (backedUpIndex && existsSync6(backupIndexPath)) {
8552
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
8439
8553
  renameSync(backupIndexPath, storeIndexPath);
8440
8554
  }
8441
- if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8555
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
8442
8556
  renameSync(backupMetadataPath, storeMetadataPath);
8443
8557
  }
8444
8558
  if (backedUpIndex || backedUpMetadata) {
@@ -8457,7 +8571,7 @@ var Indexer = class {
8457
8571
  if (!isSqliteCorruptionError(error)) {
8458
8572
  return false;
8459
8573
  }
8460
- const dbPath = path8.join(this.indexPath, "codebase.db");
8574
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8461
8575
  const warning = this.getCorruptedIndexWarning(dbPath);
8462
8576
  const errorMessage = getErrorMessage(error);
8463
8577
  if (this.config.scope === "global") {
@@ -8480,15 +8594,15 @@ var Indexer = class {
8480
8594
  this.indexCompatibility = null;
8481
8595
  this.fileHashCache.clear();
8482
8596
  const resetPaths = [
8483
- path8.join(this.indexPath, "codebase.db"),
8484
- path8.join(this.indexPath, "codebase.db-shm"),
8485
- path8.join(this.indexPath, "codebase.db-wal"),
8486
- path8.join(this.indexPath, "vectors.usearch"),
8487
- path8.join(this.indexPath, "inverted-index.json"),
8488
- path8.join(this.indexPath, "file-hashes.json"),
8489
- path8.join(this.indexPath, "failed-batches.json"),
8490
- path8.join(this.indexPath, "indexing.lock"),
8491
- path8.join(this.indexPath, "vectors")
8597
+ path12.join(this.indexPath, "codebase.db"),
8598
+ path12.join(this.indexPath, "codebase.db-shm"),
8599
+ path12.join(this.indexPath, "codebase.db-wal"),
8600
+ path12.join(this.indexPath, "vectors.usearch"),
8601
+ path12.join(this.indexPath, "inverted-index.json"),
8602
+ path12.join(this.indexPath, "file-hashes.json"),
8603
+ path12.join(this.indexPath, "failed-batches.json"),
8604
+ path12.join(this.indexPath, "indexing.lock"),
8605
+ path12.join(this.indexPath, "vectors")
8492
8606
  ];
8493
8607
  await Promise.all(resetPaths.map(async (targetPath) => {
8494
8608
  try {
@@ -8753,7 +8867,7 @@ var Indexer = class {
8753
8867
  for (const parsed of parsedFiles) {
8754
8868
  currentFilePaths.add(parsed.path);
8755
8869
  if (parsed.chunks.length === 0) {
8756
- const relativePath = path8.relative(this.projectRoot, parsed.path);
8870
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
8757
8871
  stats.parseFailures.push(relativePath);
8758
8872
  }
8759
8873
  let fileChunkCount = 0;
@@ -9036,7 +9150,7 @@ var Indexer = class {
9036
9150
  for (const requestBatch of requestBatches) {
9037
9151
  queue.add(async () => {
9038
9152
  if (rateLimitBackoffMs > 0) {
9039
- await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
9153
+ await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9040
9154
  }
9041
9155
  try {
9042
9156
  const result = await pRetry(
@@ -9597,8 +9711,8 @@ var Indexer = class {
9597
9711
  this.indexCompatibility = compatibility;
9598
9712
  return;
9599
9713
  }
9600
- const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9601
- if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9714
+ const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9715
+ if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9602
9716
  throw new Error(
9603
9717
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9604
9718
  );
@@ -9637,7 +9751,7 @@ var Indexer = class {
9637
9751
  const removedChunkKeys = [];
9638
9752
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9639
9753
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9640
- if (!existsSync6(filePath)) {
9754
+ if (!existsSync7(filePath)) {
9641
9755
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9642
9756
  for (const key of chunkKeys) {
9643
9757
  removedChunkKeys.push(key);
@@ -9686,7 +9800,7 @@ var Indexer = class {
9686
9800
  gcOrphanSymbols: 0,
9687
9801
  gcOrphanCallEdges: 0,
9688
9802
  resetCorruptedIndex: true,
9689
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9803
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9690
9804
  };
9691
9805
  }
9692
9806
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10238,12 +10352,15 @@ function formatLogs(logs) {
10238
10352
  return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10239
10353
  }).join("\n");
10240
10354
  }
10355
+ function formatResultHeader(result, index) {
10356
+ 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}`;
10357
+ }
10241
10358
  function formatDefinitionLookup(results, query) {
10242
10359
  if (results.length === 0) {
10243
10360
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
10244
10361
  }
10245
10362
  const formatted = results.map((r, idx) => {
10246
- 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}`;
10363
+ const header = formatResultHeader(r, idx);
10247
10364
  return `${header} (score: ${r.score.toFixed(2)})
10248
10365
  \`\`\`
10249
10366
  ${truncateContent(r.content)}
@@ -10253,7 +10370,7 @@ ${truncateContent(r.content)}
10253
10370
  }
10254
10371
  function formatSearchResults(results, scoreFormat = "similarity") {
10255
10372
  const formatted = results.map((r, idx) => {
10256
- 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}`;
10373
+ const header = formatResultHeader(r, idx);
10257
10374
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
10258
10375
  return `${header} ${scoreLabel}
10259
10376
  \`\`\`
@@ -10263,9 +10380,99 @@ ${truncateContent(r.content)}
10263
10380
  return formatted.join("\n\n");
10264
10381
  }
10265
10382
 
10383
+ // src/tools/knowledge-base-paths.ts
10384
+ import * as path13 from "path";
10385
+ function resolveConfigPathValue(value, baseDir) {
10386
+ const trimmed = value.trim();
10387
+ if (!trimmed) {
10388
+ return trimmed;
10389
+ }
10390
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10391
+ return path13.normalize(absolutePath);
10392
+ }
10393
+ function serializeConfigPathValue(value, baseDir) {
10394
+ const trimmed = value.trim();
10395
+ if (!trimmed) {
10396
+ return trimmed;
10397
+ }
10398
+ if (!path13.isAbsolute(trimmed)) {
10399
+ return normalizePathSeparators(path13.normalize(trimmed));
10400
+ }
10401
+ const relativePath = path13.relative(baseDir, trimmed);
10402
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10403
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
10404
+ }
10405
+ return path13.normalize(trimmed);
10406
+ }
10407
+ function resolveKnowledgeBasePath(value, projectRoot) {
10408
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10409
+ }
10410
+ function normalizeKnowledgeBasePath2(value, projectRoot) {
10411
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10412
+ }
10413
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10414
+ const normalizedInput = path13.normalize(inputPath);
10415
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10416
+ }
10417
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10418
+ const normalizedInput = path13.normalize(inputPath);
10419
+ return knowledgeBases.findIndex(
10420
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10421
+ );
10422
+ }
10423
+
10266
10424
  // src/tools/index.ts
10267
- import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
10268
- import * as path9 from "path";
10425
+ import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
10426
+ import * as path15 from "path";
10427
+
10428
+ // src/tools/config-state.ts
10429
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
10430
+ import * as path14 from "path";
10431
+ function normalizeKnowledgeBasePaths(config, projectRoot) {
10432
+ const normalized = { ...config };
10433
+ if (Array.isArray(normalized.knowledgeBases)) {
10434
+ normalized.knowledgeBases = normalized.knowledgeBases.map(
10435
+ (kb) => resolveConfigPathValue(kb, projectRoot)
10436
+ );
10437
+ }
10438
+ return normalized;
10439
+ }
10440
+ function toConfigRecord(rawConfig) {
10441
+ if (!rawConfig || typeof rawConfig !== "object") {
10442
+ return {};
10443
+ }
10444
+ return { ...rawConfig };
10445
+ }
10446
+ function getConfigPath(projectRoot) {
10447
+ return resolveWritableProjectConfigPath(projectRoot);
10448
+ }
10449
+ function loadRuntimeConfig(projectRoot) {
10450
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot)), projectRoot);
10451
+ }
10452
+ function loadEditableConfig(projectRoot) {
10453
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot)), projectRoot);
10454
+ }
10455
+ function saveConfig(projectRoot, config) {
10456
+ const configPath = getConfigPath(projectRoot);
10457
+ const configDir = path14.dirname(configPath);
10458
+ const configBaseDir = path14.dirname(configDir);
10459
+ if (!existsSync8(configDir)) {
10460
+ mkdirSync3(configDir, { recursive: true });
10461
+ }
10462
+ const serializableConfig = { ...config };
10463
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
10464
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10465
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
10466
+ );
10467
+ }
10468
+ writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10469
+ }
10470
+
10471
+ // src/tools/index.ts
10472
+ import * as os5 from "os";
10473
+ function ensureStringArray(value) {
10474
+ return Array.isArray(value) ? value : [];
10475
+ }
10269
10476
  var z = tool.schema;
10270
10477
  var sharedIndexer = null;
10271
10478
  var sharedProjectRoot = "";
@@ -10280,20 +10487,20 @@ function refreshIndexerFromConfig() {
10280
10487
  if (!sharedProjectRoot) {
10281
10488
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10282
10489
  }
10283
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10490
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10284
10491
  }
10285
10492
  function shouldForceLocalizeProjectIndex() {
10286
- const currentConfig = parseConfig(loadRuntimeConfig());
10493
+ const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10287
10494
  if (currentConfig.scope !== "project") {
10288
10495
  return false;
10289
10496
  }
10290
- const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10497
+ const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10291
10498
  const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10292
10499
  if (!mainRepoRoot) {
10293
10500
  return false;
10294
10501
  }
10295
- const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10296
- return !existsSync7(localIndexPath) && existsSync7(inheritedIndexPath);
10502
+ const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10503
+ return !existsSync9(localIndexPath) && existsSync9(inheritedIndexPath);
10297
10504
  }
10298
10505
  function getIndexer() {
10299
10506
  if (!sharedIndexer) {
@@ -10301,76 +10508,6 @@ function getIndexer() {
10301
10508
  }
10302
10509
  return sharedIndexer;
10303
10510
  }
10304
- function getConfigPath() {
10305
- return resolveWritableProjectConfigPath(sharedProjectRoot);
10306
- }
10307
- function normalizeConfigPathValue(value, baseDir) {
10308
- const trimmed = value.trim();
10309
- if (!trimmed) {
10310
- return trimmed;
10311
- }
10312
- const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10313
- return path9.normalize(absolutePath);
10314
- }
10315
- function serializeConfigPathValue(value, baseDir) {
10316
- const trimmed = value.trim();
10317
- if (!trimmed) {
10318
- return trimmed;
10319
- }
10320
- const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10321
- if (!path9.isAbsolute(trimmed)) {
10322
- return normalizeRelativePath(path9.normalize(trimmed));
10323
- }
10324
- const relativePath = path9.relative(baseDir, trimmed);
10325
- if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10326
- return normalizeRelativePath(path9.normalize(relativePath || "."));
10327
- }
10328
- return path9.normalize(trimmed);
10329
- }
10330
- function normalizeKnowledgeBasePaths(config) {
10331
- const normalized = { ...config };
10332
- if (Array.isArray(normalized.knowledgeBases)) {
10333
- normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10334
- return normalizeConfigPathValue(kb, sharedProjectRoot);
10335
- });
10336
- }
10337
- return normalized;
10338
- }
10339
- function loadRuntimeConfig() {
10340
- const rawConfig = loadMergedConfig(sharedProjectRoot);
10341
- const config = {};
10342
- if (rawConfig && typeof rawConfig === "object") {
10343
- for (const key of Object.keys(rawConfig)) {
10344
- config[key] = rawConfig[key];
10345
- }
10346
- }
10347
- return normalizeKnowledgeBasePaths(config);
10348
- }
10349
- function loadEditableConfig() {
10350
- const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10351
- const config = {};
10352
- if (rawConfig && typeof rawConfig === "object") {
10353
- for (const key of Object.keys(rawConfig)) {
10354
- config[key] = rawConfig[key];
10355
- }
10356
- }
10357
- return normalizeKnowledgeBasePaths(config);
10358
- }
10359
- function saveConfig(config) {
10360
- const configPath = getConfigPath();
10361
- const configDir = path9.dirname(configPath);
10362
- const configBaseDir = path9.dirname(configDir);
10363
- if (!existsSync7(configDir)) {
10364
- mkdirSync2(configDir, { recursive: true });
10365
- }
10366
- const serializableConfig = { ...config };
10367
- if (Array.isArray(serializableConfig.knowledgeBases)) {
10368
- serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10369
- (kb) => serializeConfigPathValue(kb, configBaseDir)
10370
- );
10371
- }
10372
- writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10373
- }
10374
10511
  var codebase_peek = tool({
10375
10512
  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.",
10376
10513
  args: {
@@ -10590,36 +10727,64 @@ var add_knowledge_base = tool({
10590
10727
  },
10591
10728
  async execute(args) {
10592
10729
  const inputPath = args.path.trim();
10593
- const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10594
- if (!existsSync7(resolvedPath)) {
10595
- return `Error: Directory does not exist: ${resolvedPath}`;
10730
+ const normalizedPath = path15.resolve(
10731
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10732
+ );
10733
+ if (!existsSync9(normalizedPath)) {
10734
+ return `Error: Directory does not exist: ${normalizedPath}`;
10735
+ }
10736
+ let realPath;
10737
+ try {
10738
+ realPath = realpathSync(normalizedPath);
10739
+ } catch {
10740
+ return `Error: Cannot resolve path: ${normalizedPath}`;
10741
+ }
10742
+ const blockedPrefixes = [
10743
+ "/etc",
10744
+ "/proc",
10745
+ "/sys",
10746
+ "/dev",
10747
+ "/boot",
10748
+ "/root",
10749
+ "/var/run",
10750
+ "/var/log"
10751
+ ];
10752
+ const homeDir = os5.homedir();
10753
+ const sensitiveDotDirs = [".ssh", ".gnupg", ".aws", ".config/gcloud", ".docker", ".kube"];
10754
+ for (const prefix of blockedPrefixes) {
10755
+ if (realPath === prefix || realPath.startsWith(prefix + "/")) {
10756
+ return `Error: Adding system directory as knowledge base is not allowed: ${normalizedPath}`;
10757
+ }
10758
+ }
10759
+ for (const dotDir of sensitiveDotDirs) {
10760
+ const sensitiveDir = path15.join(homeDir, dotDir);
10761
+ if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10762
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10763
+ }
10596
10764
  }
10597
10765
  try {
10598
- const stat4 = statSync2(resolvedPath);
10766
+ const stat4 = statSync3(normalizedPath);
10599
10767
  if (!stat4.isDirectory()) {
10600
- return `Error: Path is not a directory: ${resolvedPath}`;
10768
+ return `Error: Path is not a directory: ${normalizedPath}`;
10601
10769
  }
10602
10770
  } catch (error) {
10603
- return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
10771
+ return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10604
10772
  }
10605
- const config = loadEditableConfig();
10606
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10607
- const normalizedPath = path9.normalize(resolvedPath);
10608
- const alreadyExists = knowledgeBases.some(
10609
- (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
10610
- );
10773
+ const config = loadEditableConfig(sharedProjectRoot);
10774
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10775
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10611
10776
  if (alreadyExists) {
10612
- return `Knowledge base already configured: ${resolvedPath}`;
10777
+ return `Knowledge base already configured: ${normalizedPath}`;
10613
10778
  }
10614
- knowledgeBases.push(resolvedPath);
10779
+ knowledgeBases.push(normalizedPath);
10615
10780
  config.knowledgeBases = knowledgeBases;
10616
- saveConfig(config);
10781
+ saveConfig(sharedProjectRoot, config);
10617
10782
  refreshIndexerFromConfig();
10618
- let result = `${resolvedPath}
10783
+ let result = `${normalizedPath}
10619
10784
  `;
10620
10785
  result += `Total knowledge bases: ${knowledgeBases.length}
10621
10786
  `;
10622
- result += `Config saved to: ${getConfigPath()}
10787
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10623
10788
  `;
10624
10789
  result += `
10625
10790
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10630,8 +10795,8 @@ var list_knowledge_bases = tool({
10630
10795
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10631
10796
  args: {},
10632
10797
  async execute() {
10633
- const config = loadRuntimeConfig();
10634
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10798
+ const config = loadRuntimeConfig(sharedProjectRoot);
10799
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10635
10800
  if (knowledgeBases.length === 0) {
10636
10801
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
10637
10802
  }
@@ -10640,8 +10805,8 @@ var list_knowledge_bases = tool({
10640
10805
  `;
10641
10806
  for (let i = 0; i < knowledgeBases.length; i++) {
10642
10807
  const kb = knowledgeBases[i];
10643
- const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10644
- const exists = existsSync7(resolvedPath);
10808
+ const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10809
+ const exists = existsSync9(resolvedPath);
10645
10810
  result += `[${i + 1}] ${kb}
10646
10811
  `;
10647
10812
  result += ` Resolved: ${resolvedPath}
@@ -10650,7 +10815,7 @@ var list_knowledge_bases = tool({
10650
10815
  `;
10651
10816
  if (exists) {
10652
10817
  try {
10653
- const stat4 = statSync2(resolvedPath);
10818
+ const stat4 = statSync3(resolvedPath);
10654
10819
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
10655
10820
  `;
10656
10821
  } catch {
@@ -10658,7 +10823,7 @@ var list_knowledge_bases = tool({
10658
10823
  }
10659
10824
  result += "\n";
10660
10825
  }
10661
- result += `Config file: ${getConfigPath()}`;
10826
+ result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10662
10827
  return result;
10663
10828
  }
10664
10829
  });
@@ -10669,15 +10834,12 @@ var remove_knowledge_base = tool({
10669
10834
  },
10670
10835
  async execute(args) {
10671
10836
  const inputPath = args.path.trim();
10672
- const config = loadEditableConfig();
10673
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10837
+ const config = loadEditableConfig(sharedProjectRoot);
10838
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10674
10839
  if (knowledgeBases.length === 0) {
10675
10840
  return "No knowledge bases configured.";
10676
10841
  }
10677
- const normalizedInput = path9.normalize(inputPath);
10678
- const index = knowledgeBases.findIndex(
10679
- (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
10680
- );
10842
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10681
10843
  if (index === -1) {
10682
10844
  let result2 = `Knowledge base not found: ${inputPath}
10683
10845
 
@@ -10692,14 +10854,14 @@ var remove_knowledge_base = tool({
10692
10854
  }
10693
10855
  const removed = knowledgeBases.splice(index, 1)[0];
10694
10856
  config.knowledgeBases = knowledgeBases;
10695
- saveConfig(config);
10857
+ saveConfig(sharedProjectRoot, config);
10696
10858
  refreshIndexerFromConfig();
10697
10859
  let result = `Removed: ${removed}
10698
10860
 
10699
10861
  `;
10700
10862
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10701
10863
  `;
10702
- result += `Config saved to: ${getConfigPath()}
10864
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10703
10865
  `;
10704
10866
  result += `
10705
10867
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -10708,8 +10870,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
10708
10870
  });
10709
10871
 
10710
10872
  // src/commands/loader.ts
10711
- import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
10712
- import * as path10 from "path";
10873
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
10874
+ import * as path16 from "path";
10713
10875
  function parseFrontmatter(content) {
10714
10876
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10715
10877
  const match = content.match(frontmatterRegex);
@@ -10730,15 +10892,21 @@ function parseFrontmatter(content) {
10730
10892
  }
10731
10893
  function loadCommandsFromDirectory(commandsDir) {
10732
10894
  const commands = /* @__PURE__ */ new Map();
10733
- if (!existsSync8(commandsDir)) {
10895
+ if (!existsSync10(commandsDir)) {
10734
10896
  return commands;
10735
10897
  }
10736
10898
  const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
10737
10899
  for (const file of files) {
10738
- const filePath = path10.join(commandsDir, file);
10739
- const content = readFileSync6(filePath, "utf-8");
10900
+ const filePath = path16.join(commandsDir, file);
10901
+ let content;
10902
+ try {
10903
+ content = readFileSync7(filePath, "utf-8");
10904
+ } catch (error) {
10905
+ const message = error instanceof Error ? error.message : String(error);
10906
+ throw new Error(`Failed to load command file ${filePath}: ${message}`);
10907
+ }
10740
10908
  const { frontmatter, body } = parseFrontmatter(content);
10741
- const name = path10.basename(file, ".md");
10909
+ const name = path16.basename(file, ".md");
10742
10910
  const description = frontmatter.description || `Run the ${name} command`;
10743
10911
  commands.set(name, {
10744
10912
  description,
@@ -10748,7 +10916,7 @@ function loadCommandsFromDirectory(commandsDir) {
10748
10916
  return commands;
10749
10917
  }
10750
10918
 
10751
- // src/routing-hints.ts
10919
+ // src/routing-hints-patterns.ts
10752
10920
  var EXTERNAL_HINTS = [
10753
10921
  "docs",
10754
10922
  "documentation",
@@ -10839,6 +11007,8 @@ var SNAKE_PATTERN = /\b[a-z0-9]+_[a-z0-9_]+\b/g;
10839
11007
  var KEBAB_PATTERN = /\b[a-z0-9]+-[a-z0-9-]+\b/g;
10840
11008
  var BACKTICK_IDENTIFIER_PATTERN = /`([^`]+)`/g;
10841
11009
  var BACKTICK_IDENTIFIER_PRESENCE_PATTERN = /`([^`]+)`/;
11010
+ var DOUBLE_QUOTED_PATTERN = /"[^"]+"/;
11011
+ var SINGLE_QUOTED_PATTERN = /'[^']+'/;
10842
11012
  function normalizeText(text) {
10843
11013
  return text.trim().replace(/\s+/g, " ");
10844
11014
  }
@@ -10851,6 +11021,21 @@ function countWords(text) {
10851
11021
  }
10852
11022
  return text.split(/\s+/).filter(Boolean).length;
10853
11023
  }
11024
+ function isExternalLookup(text) {
11025
+ return URL_PATTERN.test(text) || includesHint(text, EXTERNAL_HINTS);
11026
+ }
11027
+ function hasConceptualDiscoveryHint(text) {
11028
+ return includesHint(text, CONCEPTUAL_DISCOVERY_HINTS);
11029
+ }
11030
+ function hasDefinitionHint(text) {
11031
+ return includesHint(text, DEFINITION_HINTS);
11032
+ }
11033
+ function hasExactMatchHint(text) {
11034
+ return includesHint(text, EXACT_MATCH_HINTS);
11035
+ }
11036
+ function hasNonDiscoveryHint(text) {
11037
+ return includesHint(text, NON_DISCOVERY_HINTS);
11038
+ }
10854
11039
  function hasIdentifierShape(text) {
10855
11040
  const matches = [
10856
11041
  ...text.match(CAMEL_OR_PASCAL_PATTERN) ?? [],
@@ -10866,11 +11051,13 @@ function hasIdentifierShape(text) {
10866
11051
  });
10867
11052
  }
10868
11053
  function containsQuotedIdentifier(text) {
10869
- return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || /"[^"]+"/.test(text) || /'[^']+'/.test(text);
11054
+ return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || DOUBLE_QUOTED_PATTERN.test(text) || SINGLE_QUOTED_PATTERN.test(text);
10870
11055
  }
10871
11056
  function looksLikeDirectPath(text) {
10872
11057
  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);
10873
11058
  }
11059
+
11060
+ // src/routing-hints.ts
10874
11061
  function extractUserText(parts) {
10875
11062
  return normalizeText(
10876
11063
  parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join(" ")
@@ -10886,21 +11073,21 @@ function assessRoutingIntent(text) {
10886
11073
  reason: "empty_text"
10887
11074
  };
10888
11075
  }
10889
- if (URL_PATTERN.test(lowered) || includesHint(lowered, EXTERNAL_HINTS)) {
11076
+ if (isExternalLookup(lowered)) {
10890
11077
  return {
10891
11078
  intent: "external",
10892
11079
  text: normalizedText,
10893
11080
  reason: "external_lookup"
10894
11081
  };
10895
11082
  }
10896
- const hasConceptualHint = includesHint(lowered, CONCEPTUAL_DISCOVERY_HINTS);
10897
- const hasDefinitionHint = includesHint(lowered, DEFINITION_HINTS);
10898
- const hasExactMatchHint = includesHint(lowered, EXACT_MATCH_HINTS);
10899
- const hasNonDiscoveryHint = includesHint(lowered, NON_DISCOVERY_HINTS);
11083
+ const matchedConceptualHint = hasConceptualDiscoveryHint(lowered);
11084
+ const matchedDefinitionHint = hasDefinitionHint(lowered);
11085
+ const matchedExactMatchHint = hasExactMatchHint(lowered);
11086
+ const matchedNonDiscoveryHint = hasNonDiscoveryHint(lowered);
10900
11087
  const hasIdentifier = hasIdentifierShape(normalizedText);
10901
11088
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
10902
11089
  const shortQuery = countWords(lowered) <= 10;
10903
- if (hasNonDiscoveryHint && !hasConceptualHint) {
11090
+ if (matchedNonDiscoveryHint && !matchedConceptualHint) {
10904
11091
  return {
10905
11092
  intent: "other",
10906
11093
  text: normalizedText,
@@ -10914,21 +11101,21 @@ function assessRoutingIntent(text) {
10914
11101
  reason: "direct_path_reference"
10915
11102
  };
10916
11103
  }
10917
- if ((hasDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
11104
+ if ((matchedDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
10918
11105
  return {
10919
11106
  intent: "definition_lookup",
10920
11107
  text: normalizedText,
10921
11108
  reason: "definition_lookup_request"
10922
11109
  };
10923
11110
  }
10924
- if ((hasExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !hasConceptualHint && shortQuery) {
11111
+ if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
10925
11112
  return {
10926
11113
  intent: "exact_identifier",
10927
11114
  text: normalizedText,
10928
- reason: hasExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
11115
+ reason: matchedExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
10929
11116
  };
10930
11117
  }
10931
- if (hasConceptualHint) {
11118
+ if (matchedConceptualHint) {
10932
11119
  return {
10933
11120
  intent: "local_conceptual",
10934
11121
  text: normalizedText,
@@ -11025,9 +11212,9 @@ function replaceActiveWatcher(nextWatcher) {
11025
11212
  function getCommandsDir() {
11026
11213
  let currentDir = process.cwd();
11027
11214
  if (typeof import.meta !== "undefined" && import.meta.url) {
11028
- currentDir = path11.dirname(fileURLToPath2(import.meta.url));
11215
+ currentDir = path17.dirname(fileURLToPath2(import.meta.url));
11029
11216
  }
11030
- return path11.join(currentDir, "..", "commands");
11217
+ return path17.join(currentDir, "..", "commands");
11031
11218
  }
11032
11219
  var plugin = async ({ directory }) => {
11033
11220
  try {