opencode-codebase-index 0.8.1 → 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.cjs CHANGED
@@ -329,7 +329,7 @@ var require_ignore = __commonJS({
329
329
  // path matching.
330
330
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
331
331
  // @returns {TestResult} true if a file is ignored
332
- test(path12, checkUnignored, mode) {
332
+ test(path18, checkUnignored, mode) {
333
333
  let ignored = false;
334
334
  let unignored = false;
335
335
  let matchedRule;
@@ -338,7 +338,7 @@ var require_ignore = __commonJS({
338
338
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
339
339
  return;
340
340
  }
341
- const matched = rule[mode].test(path12);
341
+ const matched = rule[mode].test(path18);
342
342
  if (!matched) {
343
343
  return;
344
344
  }
@@ -359,17 +359,17 @@ var require_ignore = __commonJS({
359
359
  var throwError = (message, Ctor) => {
360
360
  throw new Ctor(message);
361
361
  };
362
- var checkPath = (path12, originalPath, doThrow) => {
363
- if (!isString(path12)) {
362
+ var checkPath = (path18, originalPath, doThrow) => {
363
+ if (!isString(path18)) {
364
364
  return doThrow(
365
365
  `path must be a string, but got \`${originalPath}\``,
366
366
  TypeError
367
367
  );
368
368
  }
369
- if (!path12) {
369
+ if (!path18) {
370
370
  return doThrow(`path must not be empty`, TypeError);
371
371
  }
372
- if (checkPath.isNotRelative(path12)) {
372
+ if (checkPath.isNotRelative(path18)) {
373
373
  const r = "`path.relative()`d";
374
374
  return doThrow(
375
375
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -378,7 +378,7 @@ var require_ignore = __commonJS({
378
378
  }
379
379
  return true;
380
380
  };
381
- var isNotRelative = (path12) => REGEX_TEST_INVALID_PATH.test(path12);
381
+ var isNotRelative = (path18) => REGEX_TEST_INVALID_PATH.test(path18);
382
382
  checkPath.isNotRelative = isNotRelative;
383
383
  checkPath.convert = (p) => p;
384
384
  var Ignore2 = class {
@@ -408,19 +408,19 @@ var require_ignore = __commonJS({
408
408
  }
409
409
  // @returns {TestResult}
410
410
  _test(originalPath, cache, checkUnignored, slices) {
411
- const path12 = originalPath && checkPath.convert(originalPath);
411
+ const path18 = originalPath && checkPath.convert(originalPath);
412
412
  checkPath(
413
- path12,
413
+ path18,
414
414
  originalPath,
415
415
  this._strictPathCheck ? throwError : RETURN_FALSE
416
416
  );
417
- return this._t(path12, cache, checkUnignored, slices);
417
+ return this._t(path18, cache, checkUnignored, slices);
418
418
  }
419
- checkIgnore(path12) {
420
- if (!REGEX_TEST_TRAILING_SLASH.test(path12)) {
421
- return this.test(path12);
419
+ checkIgnore(path18) {
420
+ if (!REGEX_TEST_TRAILING_SLASH.test(path18)) {
421
+ return this.test(path18);
422
422
  }
423
- const slices = path12.split(SLASH2).filter(Boolean);
423
+ const slices = path18.split(SLASH2).filter(Boolean);
424
424
  slices.pop();
425
425
  if (slices.length) {
426
426
  const parent = this._t(
@@ -433,18 +433,18 @@ var require_ignore = __commonJS({
433
433
  return parent;
434
434
  }
435
435
  }
436
- return this._rules.test(path12, false, MODE_CHECK_IGNORE);
436
+ return this._rules.test(path18, false, MODE_CHECK_IGNORE);
437
437
  }
438
- _t(path12, cache, checkUnignored, slices) {
439
- if (path12 in cache) {
440
- return cache[path12];
438
+ _t(path18, cache, checkUnignored, slices) {
439
+ if (path18 in cache) {
440
+ return cache[path18];
441
441
  }
442
442
  if (!slices) {
443
- slices = path12.split(SLASH2).filter(Boolean);
443
+ slices = path18.split(SLASH2).filter(Boolean);
444
444
  }
445
445
  slices.pop();
446
446
  if (!slices.length) {
447
- return cache[path12] = this._rules.test(path12, checkUnignored, MODE_IGNORE);
447
+ return cache[path18] = this._rules.test(path18, checkUnignored, MODE_IGNORE);
448
448
  }
449
449
  const parent = this._t(
450
450
  slices.join(SLASH2) + SLASH2,
@@ -452,29 +452,29 @@ var require_ignore = __commonJS({
452
452
  checkUnignored,
453
453
  slices
454
454
  );
455
- return cache[path12] = parent.ignored ? parent : this._rules.test(path12, checkUnignored, MODE_IGNORE);
455
+ return cache[path18] = parent.ignored ? parent : this._rules.test(path18, checkUnignored, MODE_IGNORE);
456
456
  }
457
- ignores(path12) {
458
- return this._test(path12, this._ignoreCache, false).ignored;
457
+ ignores(path18) {
458
+ return this._test(path18, this._ignoreCache, false).ignored;
459
459
  }
460
460
  createFilter() {
461
- return (path12) => !this.ignores(path12);
461
+ return (path18) => !this.ignores(path18);
462
462
  }
463
463
  filter(paths) {
464
464
  return makeArray(paths).filter(this.createFilter());
465
465
  }
466
466
  // @returns {TestResult}
467
- test(path12) {
468
- return this._test(path12, this._testCache, true);
467
+ test(path18) {
468
+ return this._test(path18, this._testCache, true);
469
469
  }
470
470
  };
471
471
  var factory = (options) => new Ignore2(options);
472
- var isPathValid = (path12) => checkPath(path12 && checkPath.convert(path12), path12, RETURN_FALSE);
472
+ var isPathValid = (path18) => checkPath(path18 && checkPath.convert(path18), path18, RETURN_FALSE);
473
473
  var setupWindows = () => {
474
474
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
475
475
  checkPath.convert = makePosix;
476
476
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
477
- checkPath.isNotRelative = (path12) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path12) || isNotRelative(path12);
477
+ checkPath.isNotRelative = (path18) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path18) || isNotRelative(path18);
478
478
  };
479
479
  if (
480
480
  // Detect `process` so that it can run in browsers.
@@ -657,7 +657,7 @@ __export(index_exports, {
657
657
  default: () => index_default
658
658
  });
659
659
  module.exports = __toCommonJS(index_exports);
660
- var path11 = __toESM(require("path"), 1);
660
+ var path17 = __toESM(require("path"), 1);
661
661
  var import_url2 = require("url");
662
662
 
663
663
  // src/config/constants.ts
@@ -674,7 +674,8 @@ var DEFAULT_INCLUDE = [
674
674
  "**/*.{md,mdx}",
675
675
  "**/*.{sh,bash,zsh}",
676
676
  "**/*.{txt,html,htm}",
677
- "**/*.zig"
677
+ "**/*.zig",
678
+ "**/*.gd"
678
679
  ];
679
680
  var DEFAULT_EXCLUDE = [
680
681
  "**/node_modules/**",
@@ -773,28 +774,7 @@ var AUTO_DETECT_PROVIDER_ORDER = [
773
774
  "google"
774
775
  ];
775
776
 
776
- // src/config/env-substitution.ts
777
- var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
778
- var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
779
- function substituteEnvString(value, keyPath) {
780
- const match = value.match(ENV_REFERENCE_PATTERN);
781
- if (!match) {
782
- if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
783
- throw new Error(
784
- `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
785
- );
786
- }
787
- return value;
788
- }
789
- const variableName = match[1];
790
- const envValue = process.env[variableName];
791
- if (envValue === void 0) {
792
- throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
793
- }
794
- return envValue;
795
- }
796
-
797
- // src/config/schema.ts
777
+ // src/config/defaults.ts
798
778
  function getDefaultIndexingConfig() {
799
779
  return {
800
780
  autoIndex: false,
@@ -826,12 +806,6 @@ function getDefaultSearchConfig() {
826
806
  routingHints: true
827
807
  };
828
808
  }
829
- function isValidFusionStrategy(value) {
830
- return value === "weighted" || value === "rrf";
831
- }
832
- function isValidRerankerProvider(value) {
833
- return value === "cohere" || value === "jina" || value === "custom";
834
- }
835
809
  function getDefaultRerankerBaseUrl(provider) {
836
810
  switch (provider) {
837
811
  case "cohere":
@@ -854,8 +828,37 @@ function getDefaultDebugConfig() {
854
828
  metrics: true
855
829
  };
856
830
  }
831
+
832
+ // src/config/env-substitution.ts
833
+ var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
834
+ var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
835
+ function substituteEnvString(value, keyPath) {
836
+ const match = value.match(ENV_REFERENCE_PATTERN);
837
+ if (!match) {
838
+ if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
839
+ throw new Error(
840
+ `Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
841
+ );
842
+ }
843
+ return value;
844
+ }
845
+ const variableName = match[1];
846
+ const envValue = process.env[variableName];
847
+ if (envValue === void 0) {
848
+ throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
849
+ }
850
+ return envValue;
851
+ }
852
+
853
+ // src/config/validators.ts
857
854
  var VALID_SCOPES = ["project", "global"];
858
855
  var VALID_LOG_LEVELS = ["error", "warn", "info", "debug"];
856
+ function isValidFusionStrategy(value) {
857
+ return value === "weighted" || value === "rrf";
858
+ }
859
+ function isValidRerankerProvider(value) {
860
+ return value === "cohere" || value === "jina" || value === "custom";
861
+ }
859
862
  function isValidProvider(value) {
860
863
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
861
864
  }
@@ -883,6 +886,8 @@ function getResolvedStringArray(value, keyPath) {
883
886
  function isValidLogLevel(value) {
884
887
  return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
885
888
  }
889
+
890
+ // src/config/schema.ts
886
891
  function parseConfig(raw) {
887
892
  const input = raw && typeof raw === "object" ? raw : {};
888
893
  const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
@@ -937,9 +942,9 @@ function parseConfig(raw) {
937
942
  const rawAdditionalInclude = input.additionalInclude;
938
943
  const additionalInclude = isStringArray(rawAdditionalInclude) ? rawAdditionalInclude.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()) : [];
939
944
  let embeddingProvider;
940
- let embeddingModel = void 0;
941
- let customProvider = void 0;
942
- let reranker = void 0;
945
+ let embeddingModel;
946
+ let customProvider;
947
+ let reranker;
943
948
  if (embeddingProviderValue === "custom") {
944
949
  embeddingProvider = "custom";
945
950
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -972,7 +977,7 @@ function parseConfig(raw) {
972
977
  embeddingProvider = embeddingProviderValue;
973
978
  const rawEmbeddingModel = input.embeddingModel;
974
979
  if (typeof rawEmbeddingModel === "string") {
975
- const embeddingModelValue = substituteEnvString(rawEmbeddingModel, "$root.embeddingModel");
980
+ const embeddingModelValue = getResolvedString(rawEmbeddingModel, "$root.embeddingModel");
976
981
  if (embeddingModelValue) {
977
982
  embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
978
983
  }
@@ -1035,16 +1040,20 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1035
1040
  );
1036
1041
 
1037
1042
  // src/config/merger.ts
1038
- var import_fs3 = require("fs");
1043
+ var import_fs4 = require("fs");
1039
1044
  var os2 = __toESM(require("os"), 1);
1040
- var path3 = __toESM(require("path"), 1);
1045
+ var path6 = __toESM(require("path"), 1);
1041
1046
 
1042
1047
  // src/config/paths.ts
1043
- var import_fs2 = require("fs");
1048
+ var import_fs3 = require("fs");
1044
1049
  var os = __toESM(require("os"), 1);
1045
- var path2 = __toESM(require("path"), 1);
1050
+ var path3 = __toESM(require("path"), 1);
1046
1051
 
1047
1052
  // src/git/index.ts
1053
+ var import_fs2 = require("fs");
1054
+ var path2 = __toESM(require("path"), 1);
1055
+
1056
+ // src/git/refs.ts
1048
1057
  var import_fs = require("fs");
1049
1058
  var path = __toESM(require("path"), 1);
1050
1059
  function readPackedRefs(gitDir) {
@@ -1077,38 +1086,40 @@ function resolveCommonGitDir(gitDir) {
1077
1086
  }
1078
1087
  return gitDir;
1079
1088
  }
1089
+
1090
+ // src/git/index.ts
1080
1091
  function resolveWorktreeMainRepoRoot(repoRoot) {
1081
1092
  const gitDir = resolveGitDir(repoRoot);
1082
1093
  if (!gitDir) {
1083
1094
  return null;
1084
1095
  }
1085
1096
  const commonGitDir = resolveCommonGitDir(gitDir);
1086
- if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1097
+ if (commonGitDir === gitDir || path2.basename(commonGitDir) !== ".git") {
1087
1098
  return null;
1088
1099
  }
1089
- const mainRepoRoot = path.dirname(commonGitDir);
1090
- if (!(0, import_fs.existsSync)(mainRepoRoot)) {
1100
+ const mainRepoRoot = path2.dirname(commonGitDir);
1101
+ if (!(0, import_fs2.existsSync)(mainRepoRoot)) {
1091
1102
  return null;
1092
1103
  }
1093
- return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1104
+ return path2.resolve(mainRepoRoot) === path2.resolve(repoRoot) ? null : mainRepoRoot;
1094
1105
  }
1095
1106
  function resolveGitDir(repoRoot) {
1096
- const gitPath = path.join(repoRoot, ".git");
1097
- if (!(0, import_fs.existsSync)(gitPath)) {
1107
+ const gitPath = path2.join(repoRoot, ".git");
1108
+ if (!(0, import_fs2.existsSync)(gitPath)) {
1098
1109
  return null;
1099
1110
  }
1100
1111
  try {
1101
- const stat4 = (0, import_fs.statSync)(gitPath);
1112
+ const stat4 = (0, import_fs2.statSync)(gitPath);
1102
1113
  if (stat4.isDirectory()) {
1103
1114
  return gitPath;
1104
1115
  }
1105
1116
  if (stat4.isFile()) {
1106
- const content = (0, import_fs.readFileSync)(gitPath, "utf-8").trim();
1117
+ const content = (0, import_fs2.readFileSync)(gitPath, "utf-8").trim();
1107
1118
  const match = content.match(/^gitdir:\s*(.+)$/);
1108
1119
  if (match) {
1109
1120
  const gitdir = match[1];
1110
- const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1111
- if ((0, import_fs.existsSync)(resolvedPath)) {
1121
+ const resolvedPath = path2.isAbsolute(gitdir) ? gitdir : path2.resolve(repoRoot, gitdir);
1122
+ if ((0, import_fs2.existsSync)(resolvedPath)) {
1112
1123
  return resolvedPath;
1113
1124
  }
1114
1125
  }
@@ -1125,12 +1136,12 @@ function getCurrentBranch(repoRoot) {
1125
1136
  if (!gitDir) {
1126
1137
  return null;
1127
1138
  }
1128
- const headPath = path.join(gitDir, "HEAD");
1129
- if (!(0, import_fs.existsSync)(headPath)) {
1139
+ const headPath = path2.join(gitDir, "HEAD");
1140
+ if (!(0, import_fs2.existsSync)(headPath)) {
1130
1141
  return null;
1131
1142
  }
1132
1143
  try {
1133
- const headContent = (0, import_fs.readFileSync)(headPath, "utf-8").trim();
1144
+ const headContent = (0, import_fs2.readFileSync)(headPath, "utf-8").trim();
1134
1145
  const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1135
1146
  if (match) {
1136
1147
  return match[1];
@@ -1149,8 +1160,8 @@ function getBaseBranch(repoRoot) {
1149
1160
  const candidates = ["main", "master", "develop", "trunk"];
1150
1161
  if (refStoreDir) {
1151
1162
  for (const candidate of candidates) {
1152
- const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1153
- if ((0, import_fs.existsSync)(refPath)) {
1163
+ const refPath = path2.join(refStoreDir, "refs", "heads", candidate);
1164
+ if ((0, import_fs2.existsSync)(refPath)) {
1154
1165
  return candidate;
1155
1166
  }
1156
1167
  const packedRefs = readPackedRefs(refStoreDir);
@@ -1170,44 +1181,44 @@ function getBranchOrDefault(repoRoot) {
1170
1181
  function getHeadPath(repoRoot) {
1171
1182
  const gitDir = resolveGitDir(repoRoot);
1172
1183
  if (gitDir) {
1173
- return path.join(gitDir, "HEAD");
1184
+ return path2.join(gitDir, "HEAD");
1174
1185
  }
1175
- return path.join(repoRoot, ".git", "HEAD");
1186
+ return path2.join(repoRoot, ".git", "HEAD");
1176
1187
  }
1177
1188
 
1178
1189
  // src/config/paths.ts
1179
- var PROJECT_CONFIG_RELATIVE_PATH = path2.join(".opencode", "codebase-index.json");
1180
- var PROJECT_INDEX_RELATIVE_PATH = path2.join(".opencode", "index");
1190
+ var PROJECT_CONFIG_RELATIVE_PATH = path3.join(".opencode", "codebase-index.json");
1191
+ var PROJECT_INDEX_RELATIVE_PATH = path3.join(".opencode", "index");
1181
1192
  function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1182
1193
  const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1183
1194
  if (!mainRepoRoot) {
1184
1195
  return null;
1185
1196
  }
1186
- const fallbackPath = path2.join(mainRepoRoot, relativePath);
1187
- return (0, import_fs2.existsSync)(fallbackPath) ? fallbackPath : null;
1197
+ const fallbackPath = path3.join(mainRepoRoot, relativePath);
1198
+ return (0, import_fs3.existsSync)(fallbackPath) ? fallbackPath : null;
1188
1199
  }
1189
1200
  function hasProjectConfig(projectRoot) {
1190
- return (0, import_fs2.existsSync)(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1201
+ return (0, import_fs3.existsSync)(path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1191
1202
  }
1192
1203
  function getGlobalIndexPath() {
1193
- return path2.join(os.homedir(), ".opencode", "global-index");
1204
+ return path3.join(os.homedir(), ".opencode", "global-index");
1194
1205
  }
1195
1206
  function resolveProjectConfigPath(projectRoot) {
1196
- const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1197
- if ((0, import_fs2.existsSync)(localConfigPath)) {
1207
+ const localConfigPath = path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1208
+ if ((0, import_fs3.existsSync)(localConfigPath)) {
1198
1209
  return localConfigPath;
1199
1210
  }
1200
1211
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1201
1212
  }
1202
1213
  function resolveWritableProjectConfigPath(projectRoot) {
1203
- return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1214
+ return path3.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1204
1215
  }
1205
1216
  function resolveProjectIndexPath(projectRoot, scope) {
1206
1217
  if (scope === "global") {
1207
1218
  return getGlobalIndexPath();
1208
1219
  }
1209
- const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1210
- if ((0, import_fs2.existsSync)(localIndexPath)) {
1220
+ const localIndexPath = path3.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1221
+ if ((0, import_fs3.existsSync)(localIndexPath)) {
1211
1222
  return localIndexPath;
1212
1223
  }
1213
1224
  if (hasProjectConfig(projectRoot)) {
@@ -1216,23 +1227,30 @@ function resolveProjectIndexPath(projectRoot, scope) {
1216
1227
  return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1217
1228
  }
1218
1229
 
1219
- // src/config/merger.ts
1220
- function loadJsonFile(filePath) {
1221
- try {
1222
- if ((0, import_fs3.existsSync)(filePath)) {
1223
- const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
1224
- return JSON.parse(content);
1225
- }
1226
- } catch {
1227
- }
1228
- return null;
1230
+ // src/config/rebase.ts
1231
+ var path5 = __toESM(require("path"), 1);
1232
+
1233
+ // src/utils/paths.ts
1234
+ var path4 = __toESM(require("path"), 1);
1235
+ function normalizePathSeparators(value) {
1236
+ return value.replace(/\\/g, "/");
1237
+ }
1238
+ function isHiddenPathSegment(part) {
1239
+ return part.startsWith(".") && part !== "." && part !== "..";
1229
1240
  }
1230
- function normalizeRelativeConfigPath(candidate) {
1231
- return candidate.replace(/\\/g, "/");
1241
+ function isBuildPathSegment(part) {
1242
+ return part.toLowerCase().includes("build");
1243
+ }
1244
+ function hasFilteredPathSegment(relativePath, separator = path4.sep) {
1245
+ return relativePath.split(separator).some(
1246
+ (part) => isHiddenPathSegment(part) || isBuildPathSegment(part)
1247
+ );
1232
1248
  }
1249
+
1250
+ // src/config/rebase.ts
1233
1251
  function isWithinRoot(rootDir, targetPath) {
1234
- const relativePath = path3.relative(rootDir, targetPath);
1235
- return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1252
+ const relativePath = path5.relative(rootDir, targetPath);
1253
+ return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
1236
1254
  }
1237
1255
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1238
1256
  if (!Array.isArray(values)) {
@@ -1243,23 +1261,107 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1243
1261
  if (!trimmed) {
1244
1262
  return trimmed;
1245
1263
  }
1246
- if (path3.isAbsolute(trimmed)) {
1264
+ if (path5.isAbsolute(trimmed)) {
1247
1265
  if (isWithinRoot(sourceRoot, trimmed)) {
1248
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1266
+ return normalizePathSeparators(path5.normalize(path5.relative(sourceRoot, trimmed) || "."));
1249
1267
  }
1250
- return path3.normalize(trimmed);
1268
+ return path5.normalize(trimmed);
1251
1269
  }
1252
- const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1270
+ const resolvedFromSource = path5.resolve(sourceRoot, trimmed);
1253
1271
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1254
- return normalizeRelativeConfigPath(path3.normalize(trimmed));
1272
+ return normalizePathSeparators(path5.normalize(trimmed));
1255
1273
  }
1256
- return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1274
+ return normalizePathSeparators(path5.normalize(path5.relative(targetRoot, resolvedFromSource)));
1257
1275
  }).filter(Boolean);
1258
1276
  }
1277
+
1278
+ // src/config/merger.ts
1279
+ var PROJECT_OVERRIDE_KEYS = [
1280
+ "embeddingProvider",
1281
+ "customProvider",
1282
+ "embeddingModel",
1283
+ "reranker",
1284
+ "include",
1285
+ "exclude",
1286
+ "indexing",
1287
+ "search",
1288
+ "debug",
1289
+ "scope"
1290
+ ];
1291
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1292
+ function isRecord(value) {
1293
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1294
+ }
1295
+ function isStringArray2(value) {
1296
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1297
+ }
1298
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1299
+ if (key in normalizedProjectConfig) {
1300
+ merged[key] = normalizedProjectConfig[key];
1301
+ return;
1302
+ }
1303
+ if (key in globalConfig) {
1304
+ merged[key] = globalConfig[key];
1305
+ }
1306
+ }
1307
+ function mergeUniqueStringArray(values) {
1308
+ return [...new Set(values.map((value) => String(value).trim()))];
1309
+ }
1310
+ function normalizeKnowledgeBasePath(value) {
1311
+ let normalized = path6.normalize(String(value).trim());
1312
+ const root = path6.parse(normalized).root;
1313
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1314
+ normalized = normalized.slice(0, -1);
1315
+ }
1316
+ return normalized;
1317
+ }
1318
+ function mergeKnowledgeBasePaths(values) {
1319
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1320
+ }
1321
+ function validateConfigLayerShape(rawConfig, filePath) {
1322
+ if (!isRecord(rawConfig)) {
1323
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1324
+ }
1325
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1326
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1327
+ }
1328
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1329
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1330
+ }
1331
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1332
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1333
+ }
1334
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1335
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1336
+ }
1337
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1338
+ const value = rawConfig[section];
1339
+ if (value !== void 0 && !isRecord(value)) {
1340
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1341
+ }
1342
+ }
1343
+ return rawConfig;
1344
+ }
1345
+ function loadJsonFile(filePath) {
1346
+ if (!(0, import_fs4.existsSync)(filePath)) {
1347
+ return null;
1348
+ }
1349
+ try {
1350
+ const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
1351
+ return validateConfigLayerShape(JSON.parse(content), filePath);
1352
+ } catch (error) {
1353
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
1354
+ throw error;
1355
+ }
1356
+ const message = error instanceof Error ? error.message : String(error);
1357
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
1358
+ }
1359
+ return null;
1360
+ }
1259
1361
  function materializeLocalProjectConfig(projectRoot, config) {
1260
- const localConfigPath = path3.join(projectRoot, ".opencode", "codebase-index.json");
1261
- (0, import_fs3.mkdirSync)(path3.dirname(localConfigPath), { recursive: true });
1262
- (0, import_fs3.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1362
+ const localConfigPath = path6.join(projectRoot, ".opencode", "codebase-index.json");
1363
+ (0, import_fs4.mkdirSync)(path6.dirname(localConfigPath), { recursive: true });
1364
+ (0, import_fs4.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1263
1365
  return localConfigPath;
1264
1366
  }
1265
1367
  function loadProjectConfigLayer(projectRoot) {
@@ -1269,7 +1371,7 @@ function loadProjectConfigLayer(projectRoot) {
1269
1371
  return {};
1270
1372
  }
1271
1373
  const normalizedConfig = { ...projectConfig };
1272
- const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1374
+ const projectConfigBaseDir = path6.dirname(path6.dirname(projectConfigPath));
1273
1375
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
1274
1376
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1275
1377
  normalizedConfig.knowledgeBases,
@@ -1281,10 +1383,22 @@ function loadProjectConfigLayer(projectRoot) {
1281
1383
  }
1282
1384
  function loadMergedConfig(projectRoot) {
1283
1385
  const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1284
- const globalConfig = loadJsonFile(globalConfigPath);
1285
1386
  const projectConfigPath = resolveProjectConfigPath(projectRoot);
1387
+ let globalConfig = null;
1388
+ let globalConfigError = null;
1389
+ try {
1390
+ globalConfig = loadJsonFile(globalConfigPath);
1391
+ } catch (error) {
1392
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
1393
+ }
1286
1394
  const projectConfig = loadJsonFile(projectConfigPath);
1287
1395
  const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1396
+ if (globalConfigError) {
1397
+ if (!projectConfig) {
1398
+ throw globalConfigError;
1399
+ }
1400
+ globalConfig = null;
1401
+ }
1288
1402
  if (!globalConfig && !projectConfig) {
1289
1403
  return {};
1290
1404
  }
@@ -1294,60 +1408,16 @@ function loadMergedConfig(projectRoot) {
1294
1408
  if (!globalConfig && projectConfig) {
1295
1409
  return normalizedProjectConfig;
1296
1410
  }
1411
+ if (!globalConfig || !projectConfig) {
1412
+ return globalConfig ?? normalizedProjectConfig;
1413
+ }
1297
1414
  const merged = { ...globalConfig };
1298
- if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
1299
- merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
1300
- } else if (globalConfig && globalConfig.embeddingProvider) {
1301
- merged.embeddingProvider = globalConfig.embeddingProvider;
1302
- }
1303
- if (projectConfig && "customProvider" in normalizedProjectConfig) {
1304
- merged.customProvider = normalizedProjectConfig.customProvider;
1305
- } else if (globalConfig && globalConfig.customProvider) {
1306
- merged.customProvider = globalConfig.customProvider;
1307
- }
1308
- if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
1309
- merged.embeddingModel = normalizedProjectConfig.embeddingModel;
1310
- } else if (globalConfig && globalConfig.embeddingModel) {
1311
- merged.embeddingModel = globalConfig.embeddingModel;
1312
- }
1313
- if (projectConfig && "reranker" in normalizedProjectConfig) {
1314
- merged.reranker = normalizedProjectConfig.reranker;
1315
- } else if (globalConfig && globalConfig.reranker) {
1316
- merged.reranker = globalConfig.reranker;
1317
- }
1318
- if (projectConfig && "include" in normalizedProjectConfig) {
1319
- merged.include = normalizedProjectConfig.include;
1320
- } else if (globalConfig && globalConfig.include) {
1321
- merged.include = globalConfig.include;
1322
- }
1323
- if (projectConfig && "exclude" in normalizedProjectConfig) {
1324
- merged.exclude = normalizedProjectConfig.exclude;
1325
- } else if (globalConfig && globalConfig.exclude) {
1326
- merged.exclude = globalConfig.exclude;
1327
- }
1328
- if (projectConfig && "indexing" in normalizedProjectConfig) {
1329
- merged.indexing = normalizedProjectConfig.indexing;
1330
- } else if (globalConfig && globalConfig.indexing) {
1331
- merged.indexing = globalConfig.indexing;
1332
- }
1333
- if (projectConfig && "search" in normalizedProjectConfig) {
1334
- merged.search = normalizedProjectConfig.search;
1335
- } else if (globalConfig && globalConfig.search) {
1336
- merged.search = globalConfig.search;
1337
- }
1338
- if (projectConfig && "debug" in normalizedProjectConfig) {
1339
- merged.debug = normalizedProjectConfig.debug;
1340
- } else if (globalConfig && globalConfig.debug) {
1341
- merged.debug = globalConfig.debug;
1342
- }
1343
- if (projectConfig && "scope" in normalizedProjectConfig) {
1344
- merged.scope = normalizedProjectConfig.scope;
1345
- } else if (globalConfig && "scope" in globalConfig) {
1346
- merged.scope = globalConfig.scope;
1415
+ for (const key of PROJECT_OVERRIDE_KEYS) {
1416
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1347
1417
  }
1348
1418
  if (projectConfig) {
1349
1419
  for (const key of Object.keys(projectConfig)) {
1350
- if (key === "embeddingProvider" || key === "customProvider" || key === "embeddingModel" || key === "reranker" || key === "include" || key === "exclude" || key === "indexing" || key === "search" || key === "debug" || key === "scope" || key === "knowledgeBases" || key === "additionalInclude") {
1420
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1351
1421
  continue;
1352
1422
  }
1353
1423
  merged[key] = normalizedProjectConfig[key];
@@ -1356,13 +1426,11 @@ function loadMergedConfig(projectRoot) {
1356
1426
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1357
1427
  const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1358
1428
  const allKbs = [...globalKbs, ...projectKbs];
1359
- const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1360
- merged.knowledgeBases = uniqueKbs;
1429
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1361
1430
  const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1362
1431
  const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1363
1432
  const allAdditional = [...globalAdditional, ...projectAdditional];
1364
- const uniqueAdditional = [...new Set(allAdditional.map((p) => String(p).trim()))];
1365
- merged.additionalInclude = uniqueAdditional;
1433
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1366
1434
  return merged;
1367
1435
  }
1368
1436
 
@@ -1456,7 +1524,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1456
1524
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1457
1525
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
1458
1526
  if (wantBigintFsStats) {
1459
- this._stat = (path12) => statMethod(path12, { bigint: true });
1527
+ this._stat = (path18) => statMethod(path18, { bigint: true });
1460
1528
  } else {
1461
1529
  this._stat = statMethod;
1462
1530
  }
@@ -1481,8 +1549,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1481
1549
  const par = this.parent;
1482
1550
  const fil = par && par.files;
1483
1551
  if (fil && fil.length > 0) {
1484
- const { path: path12, depth } = par;
1485
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1552
+ const { path: path18, depth } = par;
1553
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path18));
1486
1554
  const awaited = await Promise.all(slice);
1487
1555
  for (const entry of awaited) {
1488
1556
  if (!entry)
@@ -1522,20 +1590,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1522
1590
  this.reading = false;
1523
1591
  }
1524
1592
  }
1525
- async _exploreDir(path12, depth) {
1593
+ async _exploreDir(path18, depth) {
1526
1594
  let files;
1527
1595
  try {
1528
- files = await (0, import_promises.readdir)(path12, this._rdOptions);
1596
+ files = await (0, import_promises.readdir)(path18, this._rdOptions);
1529
1597
  } catch (error) {
1530
1598
  this._onError(error);
1531
1599
  }
1532
- return { files, depth, path: path12 };
1600
+ return { files, depth, path: path18 };
1533
1601
  }
1534
- async _formatEntry(dirent, path12) {
1602
+ async _formatEntry(dirent, path18) {
1535
1603
  let entry;
1536
1604
  const basename5 = this._isDirent ? dirent.name : dirent;
1537
1605
  try {
1538
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path12, basename5));
1606
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path18, basename5));
1539
1607
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
1540
1608
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1541
1609
  } catch (err) {
@@ -1935,16 +2003,16 @@ var delFromSet = (main, prop, item) => {
1935
2003
  };
1936
2004
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1937
2005
  var FsWatchInstances = /* @__PURE__ */ new Map();
1938
- function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
2006
+ function createFsWatchInstance(path18, options, listener, errHandler, emitRaw) {
1939
2007
  const handleEvent = (rawEvent, evPath) => {
1940
- listener(path12);
1941
- emitRaw(rawEvent, evPath, { watchedPath: path12 });
1942
- if (evPath && path12 !== evPath) {
1943
- fsWatchBroadcast(sp.resolve(path12, evPath), KEY_LISTENERS, sp.join(path12, evPath));
2008
+ listener(path18);
2009
+ emitRaw(rawEvent, evPath, { watchedPath: path18 });
2010
+ if (evPath && path18 !== evPath) {
2011
+ fsWatchBroadcast(sp.resolve(path18, evPath), KEY_LISTENERS, sp.join(path18, evPath));
1944
2012
  }
1945
2013
  };
1946
2014
  try {
1947
- return (0, import_node_fs.watch)(path12, {
2015
+ return (0, import_node_fs.watch)(path18, {
1948
2016
  persistent: options.persistent
1949
2017
  }, handleEvent);
1950
2018
  } catch (error) {
@@ -1960,12 +2028,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1960
2028
  listener(val1, val2, val3);
1961
2029
  });
1962
2030
  };
1963
- var setFsWatchListener = (path12, fullPath, options, handlers) => {
2031
+ var setFsWatchListener = (path18, fullPath, options, handlers) => {
1964
2032
  const { listener, errHandler, rawEmitter } = handlers;
1965
2033
  let cont = FsWatchInstances.get(fullPath);
1966
2034
  let watcher;
1967
2035
  if (!options.persistent) {
1968
- watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
2036
+ watcher = createFsWatchInstance(path18, options, listener, errHandler, rawEmitter);
1969
2037
  if (!watcher)
1970
2038
  return;
1971
2039
  return watcher.close.bind(watcher);
@@ -1976,7 +2044,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1976
2044
  addAndConvert(cont, KEY_RAW, rawEmitter);
1977
2045
  } else {
1978
2046
  watcher = createFsWatchInstance(
1979
- path12,
2047
+ path18,
1980
2048
  options,
1981
2049
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1982
2050
  errHandler,
@@ -1991,7 +2059,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1991
2059
  cont.watcherUnusable = true;
1992
2060
  if (isWindows && error.code === "EPERM") {
1993
2061
  try {
1994
- const fd = await (0, import_promises2.open)(path12, "r");
2062
+ const fd = await (0, import_promises2.open)(path18, "r");
1995
2063
  await fd.close();
1996
2064
  broadcastErr(error);
1997
2065
  } catch (err) {
@@ -2022,7 +2090,7 @@ var setFsWatchListener = (path12, fullPath, options, handlers) => {
2022
2090
  };
2023
2091
  };
2024
2092
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
2025
- var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2093
+ var setFsWatchFileListener = (path18, fullPath, options, handlers) => {
2026
2094
  const { listener, rawEmitter } = handlers;
2027
2095
  let cont = FsWatchFileInstances.get(fullPath);
2028
2096
  const copts = cont && cont.options;
@@ -2044,7 +2112,7 @@ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
2044
2112
  });
2045
2113
  const currmtime = curr.mtimeMs;
2046
2114
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
2047
- foreach(cont.listeners, (listener2) => listener2(path12, curr));
2115
+ foreach(cont.listeners, (listener2) => listener2(path18, curr));
2048
2116
  }
2049
2117
  })
2050
2118
  };
@@ -2074,13 +2142,13 @@ var NodeFsHandler = class {
2074
2142
  * @param listener on fs change
2075
2143
  * @returns closer for the watcher instance
2076
2144
  */
2077
- _watchWithNodeFs(path12, listener) {
2145
+ _watchWithNodeFs(path18, listener) {
2078
2146
  const opts = this.fsw.options;
2079
- const directory = sp.dirname(path12);
2080
- const basename5 = sp.basename(path12);
2147
+ const directory = sp.dirname(path18);
2148
+ const basename5 = sp.basename(path18);
2081
2149
  const parent = this.fsw._getWatchedDir(directory);
2082
2150
  parent.add(basename5);
2083
- const absolutePath = sp.resolve(path12);
2151
+ const absolutePath = sp.resolve(path18);
2084
2152
  const options = {
2085
2153
  persistent: opts.persistent
2086
2154
  };
@@ -2090,12 +2158,12 @@ var NodeFsHandler = class {
2090
2158
  if (opts.usePolling) {
2091
2159
  const enableBin = opts.interval !== opts.binaryInterval;
2092
2160
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2093
- closer = setFsWatchFileListener(path12, absolutePath, options, {
2161
+ closer = setFsWatchFileListener(path18, absolutePath, options, {
2094
2162
  listener,
2095
2163
  rawEmitter: this.fsw._emitRaw
2096
2164
  });
2097
2165
  } else {
2098
- closer = setFsWatchListener(path12, absolutePath, options, {
2166
+ closer = setFsWatchListener(path18, absolutePath, options, {
2099
2167
  listener,
2100
2168
  errHandler: this._boundHandleError,
2101
2169
  rawEmitter: this.fsw._emitRaw
@@ -2117,7 +2185,7 @@ var NodeFsHandler = class {
2117
2185
  let prevStats = stats;
2118
2186
  if (parent.has(basename5))
2119
2187
  return;
2120
- const listener = async (path12, newStats) => {
2188
+ const listener = async (path18, newStats) => {
2121
2189
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
2122
2190
  return;
2123
2191
  if (!newStats || newStats.mtimeMs === 0) {
@@ -2131,11 +2199,11 @@ var NodeFsHandler = class {
2131
2199
  this.fsw._emit(EV.CHANGE, file, newStats2);
2132
2200
  }
2133
2201
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
2134
- this.fsw._closeFile(path12);
2202
+ this.fsw._closeFile(path18);
2135
2203
  prevStats = newStats2;
2136
2204
  const closer2 = this._watchWithNodeFs(file, listener);
2137
2205
  if (closer2)
2138
- this.fsw._addPathCloser(path12, closer2);
2206
+ this.fsw._addPathCloser(path18, closer2);
2139
2207
  } else {
2140
2208
  prevStats = newStats2;
2141
2209
  }
@@ -2167,7 +2235,7 @@ var NodeFsHandler = class {
2167
2235
  * @param item basename of this item
2168
2236
  * @returns true if no more processing is needed for this entry.
2169
2237
  */
2170
- async _handleSymlink(entry, directory, path12, item) {
2238
+ async _handleSymlink(entry, directory, path18, item) {
2171
2239
  if (this.fsw.closed) {
2172
2240
  return;
2173
2241
  }
@@ -2177,7 +2245,7 @@ var NodeFsHandler = class {
2177
2245
  this.fsw._incrReadyCount();
2178
2246
  let linkPath;
2179
2247
  try {
2180
- linkPath = await (0, import_promises2.realpath)(path12);
2248
+ linkPath = await (0, import_promises2.realpath)(path18);
2181
2249
  } catch (e) {
2182
2250
  this.fsw._emitReady();
2183
2251
  return true;
@@ -2187,12 +2255,12 @@ var NodeFsHandler = class {
2187
2255
  if (dir.has(item)) {
2188
2256
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
2189
2257
  this.fsw._symlinkPaths.set(full, linkPath);
2190
- this.fsw._emit(EV.CHANGE, path12, entry.stats);
2258
+ this.fsw._emit(EV.CHANGE, path18, entry.stats);
2191
2259
  }
2192
2260
  } else {
2193
2261
  dir.add(item);
2194
2262
  this.fsw._symlinkPaths.set(full, linkPath);
2195
- this.fsw._emit(EV.ADD, path12, entry.stats);
2263
+ this.fsw._emit(EV.ADD, path18, entry.stats);
2196
2264
  }
2197
2265
  this.fsw._emitReady();
2198
2266
  return true;
@@ -2222,9 +2290,9 @@ var NodeFsHandler = class {
2222
2290
  return;
2223
2291
  }
2224
2292
  const item = entry.path;
2225
- let path12 = sp.join(directory, item);
2293
+ let path18 = sp.join(directory, item);
2226
2294
  current.add(item);
2227
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
2295
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path18, item)) {
2228
2296
  return;
2229
2297
  }
2230
2298
  if (this.fsw.closed) {
@@ -2233,11 +2301,11 @@ var NodeFsHandler = class {
2233
2301
  }
2234
2302
  if (item === target || !target && !previous.has(item)) {
2235
2303
  this.fsw._incrReadyCount();
2236
- path12 = sp.join(dir, sp.relative(dir, path12));
2237
- this._addToNodeFs(path12, initialAdd, wh, depth + 1);
2304
+ path18 = sp.join(dir, sp.relative(dir, path18));
2305
+ this._addToNodeFs(path18, initialAdd, wh, depth + 1);
2238
2306
  }
2239
2307
  }).on(EV.ERROR, this._boundHandleError);
2240
- return new Promise((resolve9, reject) => {
2308
+ return new Promise((resolve11, reject) => {
2241
2309
  if (!stream)
2242
2310
  return reject();
2243
2311
  stream.once(STR_END, () => {
@@ -2246,7 +2314,7 @@ var NodeFsHandler = class {
2246
2314
  return;
2247
2315
  }
2248
2316
  const wasThrottled = throttler ? throttler.clear() : false;
2249
- resolve9(void 0);
2317
+ resolve11(void 0);
2250
2318
  previous.getChildren().filter((item) => {
2251
2319
  return item !== directory && !current.has(item);
2252
2320
  }).forEach((item) => {
@@ -2303,13 +2371,13 @@ var NodeFsHandler = class {
2303
2371
  * @param depth Child path actually targeted for watch
2304
2372
  * @param target Child path actually targeted for watch
2305
2373
  */
2306
- async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2374
+ async _addToNodeFs(path18, initialAdd, priorWh, depth, target) {
2307
2375
  const ready = this.fsw._emitReady;
2308
- if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2376
+ if (this.fsw._isIgnored(path18) || this.fsw.closed) {
2309
2377
  ready();
2310
2378
  return false;
2311
2379
  }
2312
- const wh = this.fsw._getWatchHelpers(path12);
2380
+ const wh = this.fsw._getWatchHelpers(path18);
2313
2381
  if (priorWh) {
2314
2382
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2315
2383
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2325,8 +2393,8 @@ var NodeFsHandler = class {
2325
2393
  const follow = this.fsw.options.followSymlinks;
2326
2394
  let closer;
2327
2395
  if (stats.isDirectory()) {
2328
- const absPath = sp.resolve(path12);
2329
- const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2396
+ const absPath = sp.resolve(path18);
2397
+ const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2330
2398
  if (this.fsw.closed)
2331
2399
  return;
2332
2400
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2336,29 +2404,29 @@ var NodeFsHandler = class {
2336
2404
  this.fsw._symlinkPaths.set(absPath, targetPath);
2337
2405
  }
2338
2406
  } else if (stats.isSymbolicLink()) {
2339
- const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2407
+ const targetPath = follow ? await (0, import_promises2.realpath)(path18) : path18;
2340
2408
  if (this.fsw.closed)
2341
2409
  return;
2342
2410
  const parent = sp.dirname(wh.watchPath);
2343
2411
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2344
2412
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2345
- closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2413
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path18, wh, targetPath);
2346
2414
  if (this.fsw.closed)
2347
2415
  return;
2348
2416
  if (targetPath !== void 0) {
2349
- this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2417
+ this.fsw._symlinkPaths.set(sp.resolve(path18), targetPath);
2350
2418
  }
2351
2419
  } else {
2352
2420
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2353
2421
  }
2354
2422
  ready();
2355
2423
  if (closer)
2356
- this.fsw._addPathCloser(path12, closer);
2424
+ this.fsw._addPathCloser(path18, closer);
2357
2425
  return false;
2358
2426
  } catch (error) {
2359
2427
  if (this.fsw._handleError(error)) {
2360
2428
  ready();
2361
- return path12;
2429
+ return path18;
2362
2430
  }
2363
2431
  }
2364
2432
  }
@@ -2401,24 +2469,24 @@ function createPattern(matcher) {
2401
2469
  }
2402
2470
  return () => false;
2403
2471
  }
2404
- function normalizePath(path12) {
2405
- if (typeof path12 !== "string")
2472
+ function normalizePath(path18) {
2473
+ if (typeof path18 !== "string")
2406
2474
  throw new Error("string expected");
2407
- path12 = sp2.normalize(path12);
2408
- path12 = path12.replace(/\\/g, "/");
2475
+ path18 = sp2.normalize(path18);
2476
+ path18 = path18.replace(/\\/g, "/");
2409
2477
  let prepend = false;
2410
- if (path12.startsWith("//"))
2478
+ if (path18.startsWith("//"))
2411
2479
  prepend = true;
2412
- path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2480
+ path18 = path18.replace(DOUBLE_SLASH_RE, "/");
2413
2481
  if (prepend)
2414
- path12 = "/" + path12;
2415
- return path12;
2482
+ path18 = "/" + path18;
2483
+ return path18;
2416
2484
  }
2417
2485
  function matchPatterns(patterns, testString, stats) {
2418
- const path12 = normalizePath(testString);
2486
+ const path18 = normalizePath(testString);
2419
2487
  for (let index = 0; index < patterns.length; index++) {
2420
2488
  const pattern = patterns[index];
2421
- if (pattern(path12, stats)) {
2489
+ if (pattern(path18, stats)) {
2422
2490
  return true;
2423
2491
  }
2424
2492
  }
@@ -2456,19 +2524,19 @@ var toUnix = (string) => {
2456
2524
  }
2457
2525
  return str;
2458
2526
  };
2459
- var normalizePathToUnix = (path12) => toUnix(sp2.normalize(toUnix(path12)));
2460
- var normalizeIgnored = (cwd = "") => (path12) => {
2461
- if (typeof path12 === "string") {
2462
- return normalizePathToUnix(sp2.isAbsolute(path12) ? path12 : sp2.join(cwd, path12));
2527
+ var normalizePathToUnix = (path18) => toUnix(sp2.normalize(toUnix(path18)));
2528
+ var normalizeIgnored = (cwd = "") => (path18) => {
2529
+ if (typeof path18 === "string") {
2530
+ return normalizePathToUnix(sp2.isAbsolute(path18) ? path18 : sp2.join(cwd, path18));
2463
2531
  } else {
2464
- return path12;
2532
+ return path18;
2465
2533
  }
2466
2534
  };
2467
- var getAbsolutePath = (path12, cwd) => {
2468
- if (sp2.isAbsolute(path12)) {
2469
- return path12;
2535
+ var getAbsolutePath = (path18, cwd) => {
2536
+ if (sp2.isAbsolute(path18)) {
2537
+ return path18;
2470
2538
  }
2471
- return sp2.join(cwd, path12);
2539
+ return sp2.join(cwd, path18);
2472
2540
  };
2473
2541
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2474
2542
  var DirEntry = class {
@@ -2533,10 +2601,10 @@ var WatchHelper = class {
2533
2601
  dirParts;
2534
2602
  followSymlinks;
2535
2603
  statMethod;
2536
- constructor(path12, follow, fsw) {
2604
+ constructor(path18, follow, fsw) {
2537
2605
  this.fsw = fsw;
2538
- const watchPath = path12;
2539
- this.path = path12 = path12.replace(REPLACER_RE, "");
2606
+ const watchPath = path18;
2607
+ this.path = path18 = path18.replace(REPLACER_RE, "");
2540
2608
  this.watchPath = watchPath;
2541
2609
  this.fullWatchPath = sp2.resolve(watchPath);
2542
2610
  this.dirParts = [];
@@ -2676,20 +2744,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2676
2744
  this._closePromise = void 0;
2677
2745
  let paths = unifyPaths(paths_);
2678
2746
  if (cwd) {
2679
- paths = paths.map((path12) => {
2680
- const absPath = getAbsolutePath(path12, cwd);
2747
+ paths = paths.map((path18) => {
2748
+ const absPath = getAbsolutePath(path18, cwd);
2681
2749
  return absPath;
2682
2750
  });
2683
2751
  }
2684
- paths.forEach((path12) => {
2685
- this._removeIgnoredPath(path12);
2752
+ paths.forEach((path18) => {
2753
+ this._removeIgnoredPath(path18);
2686
2754
  });
2687
2755
  this._userIgnored = void 0;
2688
2756
  if (!this._readyCount)
2689
2757
  this._readyCount = 0;
2690
2758
  this._readyCount += paths.length;
2691
- Promise.all(paths.map(async (path12) => {
2692
- const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2759
+ Promise.all(paths.map(async (path18) => {
2760
+ const res = await this._nodeFsHandler._addToNodeFs(path18, !_internal, void 0, 0, _origAdd);
2693
2761
  if (res)
2694
2762
  this._emitReady();
2695
2763
  return res;
@@ -2711,17 +2779,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2711
2779
  return this;
2712
2780
  const paths = unifyPaths(paths_);
2713
2781
  const { cwd } = this.options;
2714
- paths.forEach((path12) => {
2715
- if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2782
+ paths.forEach((path18) => {
2783
+ if (!sp2.isAbsolute(path18) && !this._closers.has(path18)) {
2716
2784
  if (cwd)
2717
- path12 = sp2.join(cwd, path12);
2718
- path12 = sp2.resolve(path12);
2785
+ path18 = sp2.join(cwd, path18);
2786
+ path18 = sp2.resolve(path18);
2719
2787
  }
2720
- this._closePath(path12);
2721
- this._addIgnoredPath(path12);
2722
- if (this._watched.has(path12)) {
2788
+ this._closePath(path18);
2789
+ this._addIgnoredPath(path18);
2790
+ if (this._watched.has(path18)) {
2723
2791
  this._addIgnoredPath({
2724
- path: path12,
2792
+ path: path18,
2725
2793
  recursive: true
2726
2794
  });
2727
2795
  }
@@ -2785,38 +2853,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2785
2853
  * @param stats arguments to be passed with event
2786
2854
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2787
2855
  */
2788
- async _emit(event, path12, stats) {
2856
+ async _emit(event, path18, stats) {
2789
2857
  if (this.closed)
2790
2858
  return;
2791
2859
  const opts = this.options;
2792
2860
  if (isWindows)
2793
- path12 = sp2.normalize(path12);
2861
+ path18 = sp2.normalize(path18);
2794
2862
  if (opts.cwd)
2795
- path12 = sp2.relative(opts.cwd, path12);
2796
- const args = [path12];
2863
+ path18 = sp2.relative(opts.cwd, path18);
2864
+ const args = [path18];
2797
2865
  if (stats != null)
2798
2866
  args.push(stats);
2799
2867
  const awf = opts.awaitWriteFinish;
2800
2868
  let pw;
2801
- if (awf && (pw = this._pendingWrites.get(path12))) {
2869
+ if (awf && (pw = this._pendingWrites.get(path18))) {
2802
2870
  pw.lastChange = /* @__PURE__ */ new Date();
2803
2871
  return this;
2804
2872
  }
2805
2873
  if (opts.atomic) {
2806
2874
  if (event === EVENTS.UNLINK) {
2807
- this._pendingUnlinks.set(path12, [event, ...args]);
2875
+ this._pendingUnlinks.set(path18, [event, ...args]);
2808
2876
  setTimeout(() => {
2809
- this._pendingUnlinks.forEach((entry, path13) => {
2877
+ this._pendingUnlinks.forEach((entry, path19) => {
2810
2878
  this.emit(...entry);
2811
2879
  this.emit(EVENTS.ALL, ...entry);
2812
- this._pendingUnlinks.delete(path13);
2880
+ this._pendingUnlinks.delete(path19);
2813
2881
  });
2814
2882
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2815
2883
  return this;
2816
2884
  }
2817
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2885
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path18)) {
2818
2886
  event = EVENTS.CHANGE;
2819
- this._pendingUnlinks.delete(path12);
2887
+ this._pendingUnlinks.delete(path18);
2820
2888
  }
2821
2889
  }
2822
2890
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2834,16 +2902,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2834
2902
  this.emitWithAll(event, args);
2835
2903
  }
2836
2904
  };
2837
- this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2905
+ this._awaitWriteFinish(path18, awf.stabilityThreshold, event, awfEmit);
2838
2906
  return this;
2839
2907
  }
2840
2908
  if (event === EVENTS.CHANGE) {
2841
- const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2909
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path18, 50);
2842
2910
  if (isThrottled)
2843
2911
  return this;
2844
2912
  }
2845
2913
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2846
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path12) : path12;
2914
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path18) : path18;
2847
2915
  let stats2;
2848
2916
  try {
2849
2917
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -2874,23 +2942,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2874
2942
  * @param timeout duration of time to suppress duplicate actions
2875
2943
  * @returns tracking object or false if action should be suppressed
2876
2944
  */
2877
- _throttle(actionType, path12, timeout) {
2945
+ _throttle(actionType, path18, timeout) {
2878
2946
  if (!this._throttled.has(actionType)) {
2879
2947
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2880
2948
  }
2881
2949
  const action = this._throttled.get(actionType);
2882
2950
  if (!action)
2883
2951
  throw new Error("invalid throttle");
2884
- const actionPath = action.get(path12);
2952
+ const actionPath = action.get(path18);
2885
2953
  if (actionPath) {
2886
2954
  actionPath.count++;
2887
2955
  return false;
2888
2956
  }
2889
2957
  let timeoutObject;
2890
2958
  const clear = () => {
2891
- const item = action.get(path12);
2959
+ const item = action.get(path18);
2892
2960
  const count = item ? item.count : 0;
2893
- action.delete(path12);
2961
+ action.delete(path18);
2894
2962
  clearTimeout(timeoutObject);
2895
2963
  if (item)
2896
2964
  clearTimeout(item.timeoutObject);
@@ -2898,7 +2966,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2898
2966
  };
2899
2967
  timeoutObject = setTimeout(clear, timeout);
2900
2968
  const thr = { timeoutObject, clear, count: 0 };
2901
- action.set(path12, thr);
2969
+ action.set(path18, thr);
2902
2970
  return thr;
2903
2971
  }
2904
2972
  _incrReadyCount() {
@@ -2912,44 +2980,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2912
2980
  * @param event
2913
2981
  * @param awfEmit Callback to be called when ready for event to be emitted.
2914
2982
  */
2915
- _awaitWriteFinish(path12, threshold, event, awfEmit) {
2983
+ _awaitWriteFinish(path18, threshold, event, awfEmit) {
2916
2984
  const awf = this.options.awaitWriteFinish;
2917
2985
  if (typeof awf !== "object")
2918
2986
  return;
2919
2987
  const pollInterval = awf.pollInterval;
2920
2988
  let timeoutHandler;
2921
- let fullPath = path12;
2922
- if (this.options.cwd && !sp2.isAbsolute(path12)) {
2923
- fullPath = sp2.join(this.options.cwd, path12);
2989
+ let fullPath = path18;
2990
+ if (this.options.cwd && !sp2.isAbsolute(path18)) {
2991
+ fullPath = sp2.join(this.options.cwd, path18);
2924
2992
  }
2925
2993
  const now = /* @__PURE__ */ new Date();
2926
2994
  const writes = this._pendingWrites;
2927
2995
  function awaitWriteFinishFn(prevStat) {
2928
2996
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
2929
- if (err || !writes.has(path12)) {
2997
+ if (err || !writes.has(path18)) {
2930
2998
  if (err && err.code !== "ENOENT")
2931
2999
  awfEmit(err);
2932
3000
  return;
2933
3001
  }
2934
3002
  const now2 = Number(/* @__PURE__ */ new Date());
2935
3003
  if (prevStat && curStat.size !== prevStat.size) {
2936
- writes.get(path12).lastChange = now2;
3004
+ writes.get(path18).lastChange = now2;
2937
3005
  }
2938
- const pw = writes.get(path12);
3006
+ const pw = writes.get(path18);
2939
3007
  const df = now2 - pw.lastChange;
2940
3008
  if (df >= threshold) {
2941
- writes.delete(path12);
3009
+ writes.delete(path18);
2942
3010
  awfEmit(void 0, curStat);
2943
3011
  } else {
2944
3012
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2945
3013
  }
2946
3014
  });
2947
3015
  }
2948
- if (!writes.has(path12)) {
2949
- writes.set(path12, {
3016
+ if (!writes.has(path18)) {
3017
+ writes.set(path18, {
2950
3018
  lastChange: now,
2951
3019
  cancelWait: () => {
2952
- writes.delete(path12);
3020
+ writes.delete(path18);
2953
3021
  clearTimeout(timeoutHandler);
2954
3022
  return event;
2955
3023
  }
@@ -2960,8 +3028,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2960
3028
  /**
2961
3029
  * Determines whether user has asked to ignore this path.
2962
3030
  */
2963
- _isIgnored(path12, stats) {
2964
- if (this.options.atomic && DOT_RE.test(path12))
3031
+ _isIgnored(path18, stats) {
3032
+ if (this.options.atomic && DOT_RE.test(path18))
2965
3033
  return true;
2966
3034
  if (!this._userIgnored) {
2967
3035
  const { cwd } = this.options;
@@ -2971,17 +3039,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2971
3039
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2972
3040
  this._userIgnored = anymatch(list, void 0);
2973
3041
  }
2974
- return this._userIgnored(path12, stats);
3042
+ return this._userIgnored(path18, stats);
2975
3043
  }
2976
- _isntIgnored(path12, stat4) {
2977
- return !this._isIgnored(path12, stat4);
3044
+ _isntIgnored(path18, stat4) {
3045
+ return !this._isIgnored(path18, stat4);
2978
3046
  }
2979
3047
  /**
2980
3048
  * Provides a set of common helpers and properties relating to symlink handling.
2981
3049
  * @param path file or directory pattern being watched
2982
3050
  */
2983
- _getWatchHelpers(path12) {
2984
- return new WatchHelper(path12, this.options.followSymlinks, this);
3051
+ _getWatchHelpers(path18) {
3052
+ return new WatchHelper(path18, this.options.followSymlinks, this);
2985
3053
  }
2986
3054
  // Directory helpers
2987
3055
  // -----------------
@@ -3013,63 +3081,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
3013
3081
  * @param item base path of item/directory
3014
3082
  */
3015
3083
  _remove(directory, item, isDirectory) {
3016
- const path12 = sp2.join(directory, item);
3017
- const fullPath = sp2.resolve(path12);
3018
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path12) || this._watched.has(fullPath);
3019
- if (!this._throttle("remove", path12, 100))
3084
+ const path18 = sp2.join(directory, item);
3085
+ const fullPath = sp2.resolve(path18);
3086
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path18) || this._watched.has(fullPath);
3087
+ if (!this._throttle("remove", path18, 100))
3020
3088
  return;
3021
3089
  if (!isDirectory && this._watched.size === 1) {
3022
3090
  this.add(directory, item, true);
3023
3091
  }
3024
- const wp = this._getWatchedDir(path12);
3092
+ const wp = this._getWatchedDir(path18);
3025
3093
  const nestedDirectoryChildren = wp.getChildren();
3026
- nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
3094
+ nestedDirectoryChildren.forEach((nested) => this._remove(path18, nested));
3027
3095
  const parent = this._getWatchedDir(directory);
3028
3096
  const wasTracked = parent.has(item);
3029
3097
  parent.remove(item);
3030
3098
  if (this._symlinkPaths.has(fullPath)) {
3031
3099
  this._symlinkPaths.delete(fullPath);
3032
3100
  }
3033
- let relPath = path12;
3101
+ let relPath = path18;
3034
3102
  if (this.options.cwd)
3035
- relPath = sp2.relative(this.options.cwd, path12);
3103
+ relPath = sp2.relative(this.options.cwd, path18);
3036
3104
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
3037
3105
  const event = this._pendingWrites.get(relPath).cancelWait();
3038
3106
  if (event === EVENTS.ADD)
3039
3107
  return;
3040
3108
  }
3041
- this._watched.delete(path12);
3109
+ this._watched.delete(path18);
3042
3110
  this._watched.delete(fullPath);
3043
3111
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
3044
- if (wasTracked && !this._isIgnored(path12))
3045
- this._emit(eventName, path12);
3046
- this._closePath(path12);
3112
+ if (wasTracked && !this._isIgnored(path18))
3113
+ this._emit(eventName, path18);
3114
+ this._closePath(path18);
3047
3115
  }
3048
3116
  /**
3049
3117
  * Closes all watchers for a path
3050
3118
  */
3051
- _closePath(path12) {
3052
- this._closeFile(path12);
3053
- const dir = sp2.dirname(path12);
3054
- this._getWatchedDir(dir).remove(sp2.basename(path12));
3119
+ _closePath(path18) {
3120
+ this._closeFile(path18);
3121
+ const dir = sp2.dirname(path18);
3122
+ this._getWatchedDir(dir).remove(sp2.basename(path18));
3055
3123
  }
3056
3124
  /**
3057
3125
  * Closes only file-specific watchers
3058
3126
  */
3059
- _closeFile(path12) {
3060
- const closers = this._closers.get(path12);
3127
+ _closeFile(path18) {
3128
+ const closers = this._closers.get(path18);
3061
3129
  if (!closers)
3062
3130
  return;
3063
3131
  closers.forEach((closer) => closer());
3064
- this._closers.delete(path12);
3132
+ this._closers.delete(path18);
3065
3133
  }
3066
- _addPathCloser(path12, closer) {
3134
+ _addPathCloser(path18, closer) {
3067
3135
  if (!closer)
3068
3136
  return;
3069
- let list = this._closers.get(path12);
3137
+ let list = this._closers.get(path18);
3070
3138
  if (!list) {
3071
3139
  list = [];
3072
- this._closers.set(path12, list);
3140
+ this._closers.set(path18, list);
3073
3141
  }
3074
3142
  list.push(closer);
3075
3143
  }
@@ -3098,13 +3166,13 @@ function watch(paths, options = {}) {
3098
3166
  }
3099
3167
  var chokidar_default = { watch, FSWatcher };
3100
3168
 
3101
- // src/watcher/index.ts
3102
- var path5 = __toESM(require("path"), 1);
3169
+ // src/watcher/file-watcher.ts
3170
+ var path8 = __toESM(require("path"), 1);
3103
3171
 
3104
3172
  // src/utils/files.ts
3105
3173
  var import_ignore = __toESM(require_ignore(), 1);
3106
- var import_fs4 = require("fs");
3107
- var path4 = __toESM(require("path"), 1);
3174
+ var import_fs5 = require("fs");
3175
+ var path7 = __toESM(require("path"), 1);
3108
3176
  var PROJECT_MARKERS = [
3109
3177
  ".git",
3110
3178
  "package.json",
@@ -3123,7 +3191,7 @@ var PROJECT_MARKERS = [
3123
3191
  ];
3124
3192
  function hasProjectMarker(projectRoot) {
3125
3193
  for (const marker of PROJECT_MARKERS) {
3126
- if ((0, import_fs4.existsSync)(path4.join(projectRoot, marker))) {
3194
+ if ((0, import_fs5.existsSync)(path7.join(projectRoot, marker))) {
3127
3195
  return true;
3128
3196
  }
3129
3197
  }
@@ -3149,23 +3217,17 @@ function createIgnoreFilter(projectRoot) {
3149
3217
  "**/*build*/**"
3150
3218
  ];
3151
3219
  ig.add(defaultIgnores);
3152
- const gitignorePath = path4.join(projectRoot, ".gitignore");
3153
- if ((0, import_fs4.existsSync)(gitignorePath)) {
3154
- const gitignoreContent = (0, import_fs4.readFileSync)(gitignorePath, "utf-8");
3220
+ const gitignorePath = path7.join(projectRoot, ".gitignore");
3221
+ if ((0, import_fs5.existsSync)(gitignorePath)) {
3222
+ const gitignoreContent = (0, import_fs5.readFileSync)(gitignorePath, "utf-8");
3155
3223
  ig.add(gitignoreContent);
3156
3224
  }
3157
3225
  return ig;
3158
3226
  }
3159
3227
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3160
- const relativePath = path4.relative(projectRoot, filePath);
3161
- const pathParts = relativePath.split(path4.sep);
3162
- for (const part of pathParts) {
3163
- if (part.startsWith(".") && part !== "." && part !== "..") {
3164
- return false;
3165
- }
3166
- if (part.toLowerCase().includes("build")) {
3167
- return false;
3168
- }
3228
+ const relativePath = path7.relative(projectRoot, filePath);
3229
+ if (hasFilteredPathSegment(relativePath, path7.sep)) {
3230
+ return false;
3169
3231
  }
3170
3232
  if (ignoreFilter.ignores(relativePath)) {
3171
3233
  return false;
@@ -3198,19 +3260,19 @@ function matchGlob(filePath, pattern) {
3198
3260
  return regex.test(filePath);
3199
3261
  }
3200
3262
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3201
- const entries = await import_fs4.promises.readdir(dir, { withFileTypes: true });
3263
+ const entries = await import_fs5.promises.readdir(dir, { withFileTypes: true });
3202
3264
  const filesInDir = [];
3203
3265
  const subdirs = [];
3204
3266
  for (const entry of entries) {
3205
- const fullPath = path4.join(dir, entry.name);
3206
- const relativePath = path4.relative(projectRoot, fullPath);
3207
- if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
3267
+ const fullPath = path7.join(dir, entry.name);
3268
+ const relativePath = path7.relative(projectRoot, fullPath);
3269
+ if (isHiddenPathSegment(entry.name)) {
3208
3270
  if (entry.isDirectory()) {
3209
3271
  skipped.push({ path: relativePath, reason: "excluded" });
3210
3272
  }
3211
3273
  continue;
3212
3274
  }
3213
- if (entry.isDirectory() && entry.name.toLowerCase().includes("build")) {
3275
+ if (entry.isDirectory() && isBuildPathSegment(entry.name)) {
3214
3276
  skipped.push({ path: relativePath, reason: "excluded" });
3215
3277
  continue;
3216
3278
  }
@@ -3223,7 +3285,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3223
3285
  if (entry.isDirectory()) {
3224
3286
  subdirs.push({ fullPath, relativePath });
3225
3287
  } else if (entry.isFile()) {
3226
- const stat4 = await import_fs4.promises.stat(fullPath);
3288
+ const stat4 = await import_fs5.promises.stat(fullPath);
3227
3289
  if (stat4.size > maxFileSize) {
3228
3290
  skipped.push({ path: relativePath, reason: "too_large" });
3229
3291
  continue;
@@ -3252,7 +3314,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3252
3314
  yield f;
3253
3315
  }
3254
3316
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3255
- skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3317
+ skipped.push({ path: path7.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3256
3318
  }
3257
3319
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3258
3320
  if (canRecurse) {
@@ -3292,14 +3354,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3292
3354
  if (additionalRoots && additionalRoots.length > 0) {
3293
3355
  const normalizedRoots = /* @__PURE__ */ new Set();
3294
3356
  for (const kbRoot of additionalRoots) {
3295
- const resolved = path4.normalize(
3296
- path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3357
+ const resolved = path7.normalize(
3358
+ path7.isAbsolute(kbRoot) ? kbRoot : path7.resolve(projectRoot, kbRoot)
3297
3359
  );
3298
3360
  normalizedRoots.add(resolved);
3299
3361
  }
3300
3362
  for (const resolvedKbRoot of normalizedRoots) {
3301
3363
  try {
3302
- const stat4 = await import_fs4.promises.stat(resolvedKbRoot);
3364
+ const stat4 = await import_fs5.promises.stat(resolvedKbRoot);
3303
3365
  if (!stat4.isDirectory()) {
3304
3366
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3305
3367
  continue;
@@ -3326,7 +3388,7 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3326
3388
  return { files, skipped };
3327
3389
  }
3328
3390
 
3329
- // src/watcher/index.ts
3391
+ // src/watcher/file-watcher.ts
3330
3392
  var FileWatcher = class {
3331
3393
  watcher = null;
3332
3394
  projectRoot;
@@ -3347,16 +3409,10 @@ var FileWatcher = class {
3347
3409
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3348
3410
  this.watcher = chokidar_default.watch(this.projectRoot, {
3349
3411
  ignored: (filePath) => {
3350
- const relativePath = path5.relative(this.projectRoot, filePath);
3412
+ const relativePath = path8.relative(this.projectRoot, filePath);
3351
3413
  if (!relativePath) return false;
3352
- const pathParts = relativePath.split(path5.sep);
3353
- for (const part of pathParts) {
3354
- if (part.startsWith(".") && part !== "." && part !== "..") {
3355
- return true;
3356
- }
3357
- if (part.toLowerCase().includes("build")) {
3358
- return true;
3359
- }
3414
+ if (hasFilteredPathSegment(relativePath, path8.sep)) {
3415
+ return true;
3360
3416
  }
3361
3417
  if (ignoreFilter.ignores(relativePath)) {
3362
3418
  return true;
@@ -3401,7 +3457,7 @@ var FileWatcher = class {
3401
3457
  return;
3402
3458
  }
3403
3459
  const changes = Array.from(this.pendingChanges.entries()).map(
3404
- ([path12, type]) => ({ path: path12, type })
3460
+ ([path18, type]) => ({ path: path18, type })
3405
3461
  );
3406
3462
  this.pendingChanges.clear();
3407
3463
  try {
@@ -3426,6 +3482,9 @@ var FileWatcher = class {
3426
3482
  return this.watcher !== null;
3427
3483
  }
3428
3484
  };
3485
+
3486
+ // src/watcher/git-head-watcher.ts
3487
+ var path9 = __toESM(require("path"), 1);
3429
3488
  var GitHeadWatcher = class {
3430
3489
  watcher = null;
3431
3490
  projectRoot;
@@ -3447,7 +3506,7 @@ var GitHeadWatcher = class {
3447
3506
  this.onBranchChange = handler;
3448
3507
  this.currentBranch = getCurrentBranch(this.projectRoot);
3449
3508
  const headPath = getHeadPath(this.projectRoot);
3450
- const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3509
+ const refsPath = path9.join(this.projectRoot, ".git", "refs", "heads");
3451
3510
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3452
3511
  persistent: true,
3453
3512
  ignoreInitial: true,
@@ -3499,6 +3558,8 @@ var GitHeadWatcher = class {
3499
3558
  return this.watcher !== null;
3500
3559
  }
3501
3560
  };
3561
+
3562
+ // src/watcher/index.ts
3502
3563
  function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3503
3564
  const fileWatcher = new FileWatcher(projectRoot, config);
3504
3565
  fileWatcher.start(async (changes) => {
@@ -3532,8 +3593,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3532
3593
  var import_plugin = require("@opencode-ai/plugin");
3533
3594
 
3534
3595
  // src/indexer/index.ts
3535
- var import_fs6 = require("fs");
3536
- var path8 = __toESM(require("path"), 1);
3596
+ var import_fs7 = require("fs");
3597
+ var path12 = __toESM(require("path"), 1);
3537
3598
  var import_perf_hooks = require("perf_hooks");
3538
3599
 
3539
3600
  // node_modules/eventemitter3/index.mjs
@@ -3558,7 +3619,7 @@ function pTimeout(promise, options) {
3558
3619
  } = options;
3559
3620
  let timer;
3560
3621
  let abortHandler;
3561
- const wrappedPromise = new Promise((resolve9, reject) => {
3622
+ const wrappedPromise = new Promise((resolve11, reject) => {
3562
3623
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3563
3624
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3564
3625
  }
@@ -3572,7 +3633,7 @@ function pTimeout(promise, options) {
3572
3633
  };
3573
3634
  signal.addEventListener("abort", abortHandler, { once: true });
3574
3635
  }
3575
- promise.then(resolve9, reject);
3636
+ promise.then(resolve11, reject);
3576
3637
  if (milliseconds === Number.POSITIVE_INFINITY) {
3577
3638
  return;
3578
3639
  }
@@ -3580,7 +3641,7 @@ function pTimeout(promise, options) {
3580
3641
  timer = customTimers.setTimeout.call(void 0, () => {
3581
3642
  if (fallback) {
3582
3643
  try {
3583
- resolve9(fallback());
3644
+ resolve11(fallback());
3584
3645
  } catch (error) {
3585
3646
  reject(error);
3586
3647
  }
@@ -3590,7 +3651,7 @@ function pTimeout(promise, options) {
3590
3651
  promise.cancel();
3591
3652
  }
3592
3653
  if (message === false) {
3593
- resolve9();
3654
+ resolve11();
3594
3655
  } else if (message instanceof Error) {
3595
3656
  reject(message);
3596
3657
  } else {
@@ -3992,7 +4053,7 @@ var PQueue = class extends import_index.default {
3992
4053
  // Assign unique ID if not provided
3993
4054
  id: options.id ?? (this.#idAssigner++).toString()
3994
4055
  };
3995
- return new Promise((resolve9, reject) => {
4056
+ return new Promise((resolve11, reject) => {
3996
4057
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3997
4058
  let cleanupQueueAbortHandler = () => void 0;
3998
4059
  const run = async () => {
@@ -4032,7 +4093,7 @@ var PQueue = class extends import_index.default {
4032
4093
  })]);
4033
4094
  }
4034
4095
  const result = await operation;
4035
- resolve9(result);
4096
+ resolve11(result);
4036
4097
  this.emit("completed", result);
4037
4098
  } catch (error) {
4038
4099
  reject(error);
@@ -4220,13 +4281,13 @@ var PQueue = class extends import_index.default {
4220
4281
  });
4221
4282
  }
4222
4283
  async #onEvent(event, filter) {
4223
- return new Promise((resolve9) => {
4284
+ return new Promise((resolve11) => {
4224
4285
  const listener = () => {
4225
4286
  if (filter && !filter()) {
4226
4287
  return;
4227
4288
  }
4228
4289
  this.off(event, listener);
4229
- resolve9();
4290
+ resolve11();
4230
4291
  };
4231
4292
  this.on(event, listener);
4232
4293
  });
@@ -4512,7 +4573,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4512
4573
  const finalDelay = Math.min(delayTime, remainingTime);
4513
4574
  options.signal?.throwIfAborted();
4514
4575
  if (finalDelay > 0) {
4515
- await new Promise((resolve9, reject) => {
4576
+ await new Promise((resolve11, reject) => {
4516
4577
  const onAbort = () => {
4517
4578
  clearTimeout(timeoutToken);
4518
4579
  options.signal?.removeEventListener("abort", onAbort);
@@ -4520,7 +4581,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4520
4581
  };
4521
4582
  const timeoutToken = setTimeout(() => {
4522
4583
  options.signal?.removeEventListener("abort", onAbort);
4523
- resolve9();
4584
+ resolve11();
4524
4585
  }, finalDelay);
4525
4586
  if (options.unref) {
4526
4587
  timeoutToken.unref?.();
@@ -4581,17 +4642,17 @@ async function pRetry(input, options = {}) {
4581
4642
  }
4582
4643
 
4583
4644
  // src/embeddings/detector.ts
4584
- var import_fs5 = require("fs");
4585
- var path6 = __toESM(require("path"), 1);
4645
+ var import_fs6 = require("fs");
4646
+ var path10 = __toESM(require("path"), 1);
4586
4647
  var os3 = __toESM(require("os"), 1);
4587
4648
  function getOpenCodeAuthPath() {
4588
- return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4649
+ return path10.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4589
4650
  }
4590
4651
  function loadOpenCodeAuth() {
4591
4652
  const authPath = getOpenCodeAuthPath();
4592
4653
  try {
4593
- if ((0, import_fs5.existsSync)(authPath)) {
4594
- return JSON.parse((0, import_fs5.readFileSync)(authPath, "utf-8"));
4654
+ if ((0, import_fs6.existsSync)(authPath)) {
4655
+ return JSON.parse((0, import_fs6.readFileSync)(authPath, "utf-8"));
4595
4656
  }
4596
4657
  } catch {
4597
4658
  }
@@ -4658,7 +4719,8 @@ function getGitHubCopilotCredentials() {
4658
4719
  if (!copilotAuth || copilotAuth.type !== "oauth") {
4659
4720
  return null;
4660
4721
  }
4661
- const baseUrl = copilotAuth.enterpriseUrl ? `https://copilot-api.${copilotAuth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4722
+ const auth = copilotAuth;
4723
+ const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
4662
4724
  return {
4663
4725
  provider: "github-copilot",
4664
4726
  baseUrl,
@@ -4754,42 +4816,12 @@ function createCustomProviderInfo(config) {
4754
4816
  };
4755
4817
  }
4756
4818
 
4757
- // src/embeddings/provider.ts
4758
- var CustomProviderNonRetryableError = class extends Error {
4759
- constructor(message) {
4760
- super(message);
4761
- this.name = "CustomProviderNonRetryableError";
4762
- }
4763
- };
4764
- function createEmbeddingProvider(configuredProviderInfo) {
4765
- switch (configuredProviderInfo.provider) {
4766
- case "github-copilot":
4767
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4768
- case "openai":
4769
- return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4770
- case "google":
4771
- return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4772
- case "ollama":
4773
- return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4774
- case "custom":
4775
- return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
4776
- default: {
4777
- const _exhaustive = configuredProviderInfo;
4778
- throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
4779
- }
4780
- }
4781
- }
4782
- var GitHubCopilotEmbeddingProvider = class {
4819
+ // src/embeddings/provider-types.ts
4820
+ var BaseEmbeddingProvider = class {
4783
4821
  constructor(credentials, modelInfo) {
4784
4822
  this.credentials = credentials;
4785
4823
  this.modelInfo = modelInfo;
4786
4824
  }
4787
- getToken() {
4788
- if (!this.credentials.refreshToken) {
4789
- throw new Error("No OAuth token available for GitHub");
4790
- }
4791
- return this.credentials.refreshToken;
4792
- }
4793
4825
  async embedQuery(query) {
4794
4826
  const result = await this.embedBatch([query]);
4795
4827
  return {
@@ -4804,69 +4836,204 @@ var GitHubCopilotEmbeddingProvider = class {
4804
4836
  tokensUsed: result.totalTokensUsed
4805
4837
  };
4806
4838
  }
4807
- async embedBatch(texts) {
4808
- const token = this.getToken();
4809
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4810
- method: "POST",
4811
- headers: {
4812
- Authorization: `Bearer ${token}`,
4813
- "Content-Type": "application/json",
4814
- Accept: "application/vnd.github+json",
4815
- "X-GitHub-Api-Version": "2022-11-28"
4816
- },
4817
- body: JSON.stringify({
4818
- model: `openai/${this.modelInfo.model}`,
4819
- input: texts
4820
- })
4821
- });
4822
- if (!response.ok) {
4823
- const error = await response.text();
4824
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4825
- }
4826
- const data = await response.json();
4827
- return {
4828
- embeddings: data.data.map((d) => d.embedding),
4829
- totalTokensUsed: data.usage.total_tokens
4830
- };
4831
- }
4832
4839
  getModelInfo() {
4833
4840
  return this.modelInfo;
4834
4841
  }
4835
4842
  };
4836
- var OpenAIEmbeddingProvider = class {
4843
+ var CustomProviderNonRetryableError = class extends Error {
4844
+ constructor(message) {
4845
+ super(message);
4846
+ this.name = "CustomProviderNonRetryableError";
4847
+ }
4848
+ };
4849
+
4850
+ // src/utils/url-validation.ts
4851
+ var BLOCKED_METADATA_IPS = [
4852
+ /^169\.254\.169\.254$/,
4853
+ // AWS/Azure/GCP metadata
4854
+ /^169\.254\.170\.2$/,
4855
+ // AWS ECS task metadata
4856
+ /^fd00:ec2::254$/
4857
+ // AWS IMDSv2 IPv6
4858
+ ];
4859
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
4860
+ "metadata.google.internal",
4861
+ "metadata.google",
4862
+ "metadata.goog",
4863
+ "kubernetes.default.svc"
4864
+ ]);
4865
+ function validateExternalUrl(urlString) {
4866
+ let parsed;
4867
+ try {
4868
+ parsed = new URL(urlString);
4869
+ } catch {
4870
+ return { valid: false, reason: `Invalid URL: ${sanitizeUrlForError(urlString)}` };
4871
+ }
4872
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
4873
+ return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
4874
+ }
4875
+ const hostname = parsed.hostname.toLowerCase();
4876
+ if (BLOCKED_HOSTNAMES.has(hostname)) {
4877
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
4878
+ }
4879
+ for (const pattern of BLOCKED_METADATA_IPS) {
4880
+ if (pattern.test(hostname)) {
4881
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
4882
+ }
4883
+ }
4884
+ if (/^169\.254\./.test(hostname)) {
4885
+ return { valid: false, reason: `Blocked: link-local address (${hostname})` };
4886
+ }
4887
+ return { valid: true };
4888
+ }
4889
+ function sanitizeUrlForError(url) {
4890
+ try {
4891
+ const parsed = new URL(url);
4892
+ parsed.username = "";
4893
+ parsed.password = "";
4894
+ return parsed.toString();
4895
+ } catch {
4896
+ const maxLen = 80;
4897
+ if (url.length > maxLen) {
4898
+ return url.slice(0, maxLen) + "...";
4899
+ }
4900
+ return url;
4901
+ }
4902
+ }
4903
+
4904
+ // src/embeddings/providers/custom.ts
4905
+ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
4837
4906
  constructor(credentials, modelInfo) {
4838
- this.credentials = credentials;
4839
- this.modelInfo = modelInfo;
4907
+ super(credentials, modelInfo);
4840
4908
  }
4841
- async embedQuery(query) {
4842
- const result = await this.embedBatch([query]);
4843
- return {
4844
- embedding: result.embeddings[0],
4845
- tokensUsed: result.totalTokensUsed
4909
+ splitIntoRequestBatches(texts) {
4910
+ const maxBatchSize = this.modelInfo.maxBatchSize;
4911
+ if (!maxBatchSize || texts.length <= maxBatchSize) {
4912
+ return [texts];
4913
+ }
4914
+ const batches = [];
4915
+ for (let i = 0; i < texts.length; i += maxBatchSize) {
4916
+ batches.push(texts.slice(i, i + maxBatchSize));
4917
+ }
4918
+ return batches;
4919
+ }
4920
+ async embedRequest(texts) {
4921
+ if (texts.length === 0) {
4922
+ return {
4923
+ embeddings: [],
4924
+ totalTokensUsed: 0
4925
+ };
4926
+ }
4927
+ const headers = {
4928
+ "Content-Type": "application/json"
4846
4929
  };
4930
+ if (this.credentials.apiKey) {
4931
+ headers.Authorization = `Bearer ${this.credentials.apiKey}`;
4932
+ }
4933
+ const baseUrl = this.credentials.baseUrl ?? "";
4934
+ const fullUrl = `${baseUrl}/embeddings`;
4935
+ const urlCheck = validateExternalUrl(fullUrl);
4936
+ if (!urlCheck.valid) {
4937
+ throw new CustomProviderNonRetryableError(
4938
+ `Custom embedding provider URL blocked (SSRF protection): ${urlCheck.reason}`
4939
+ );
4940
+ }
4941
+ const timeoutMs = this.modelInfo.timeoutMs;
4942
+ const controller = new AbortController();
4943
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
4944
+ let response;
4945
+ try {
4946
+ response = await fetch(fullUrl, {
4947
+ method: "POST",
4948
+ headers,
4949
+ body: JSON.stringify({
4950
+ model: this.modelInfo.model,
4951
+ input: texts
4952
+ }),
4953
+ signal: controller.signal
4954
+ });
4955
+ } catch (error) {
4956
+ if (error instanceof Error && error.name === "AbortError") {
4957
+ throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${sanitizeUrlForError(fullUrl)}`);
4958
+ }
4959
+ throw error;
4960
+ } finally {
4961
+ clearTimeout(timeout);
4962
+ }
4963
+ if (!response.ok) {
4964
+ const errorText = (await response.text()).slice(0, 500);
4965
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4966
+ throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
4967
+ }
4968
+ throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
4969
+ }
4970
+ const data = await response.json();
4971
+ if (data.data && Array.isArray(data.data)) {
4972
+ if (data.data.length > 0) {
4973
+ const actualDims = data.data[0].embedding.length;
4974
+ if (actualDims !== this.modelInfo.dimensions) {
4975
+ throw new Error(
4976
+ `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.`
4977
+ );
4978
+ }
4979
+ }
4980
+ if (data.data.length !== texts.length) {
4981
+ throw new Error(
4982
+ `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
4983
+ );
4984
+ }
4985
+ return {
4986
+ embeddings: data.data.map((d) => d.embedding),
4987
+ totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
4988
+ };
4989
+ }
4990
+ throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
4847
4991
  }
4848
- async embedDocument(document) {
4849
- const result = await this.embedBatch([document]);
4992
+ async embedBatch(texts) {
4993
+ const requestBatches = this.splitIntoRequestBatches(texts);
4994
+ const embeddings = [];
4995
+ let totalTokensUsed = 0;
4996
+ for (const batch of requestBatches) {
4997
+ const result = await this.embedRequest(batch);
4998
+ embeddings.push(...result.embeddings);
4999
+ totalTokensUsed += result.totalTokensUsed;
5000
+ }
4850
5001
  return {
4851
- embedding: result.embeddings[0],
4852
- tokensUsed: result.totalTokensUsed
5002
+ embeddings,
5003
+ totalTokensUsed
4853
5004
  };
4854
5005
  }
5006
+ };
5007
+
5008
+ // src/embeddings/providers/github-copilot.ts
5009
+ var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
5010
+ constructor(credentials, modelInfo) {
5011
+ super(credentials, modelInfo);
5012
+ }
5013
+ getToken() {
5014
+ if (!this.credentials.refreshToken) {
5015
+ throw new Error("No OAuth token available for GitHub");
5016
+ }
5017
+ return this.credentials.refreshToken;
5018
+ }
4855
5019
  async embedBatch(texts) {
4856
- const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5020
+ const token = this.getToken();
5021
+ const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
4857
5022
  method: "POST",
4858
5023
  headers: {
4859
- Authorization: `Bearer ${this.credentials.apiKey}`,
4860
- "Content-Type": "application/json"
5024
+ Authorization: `Bearer ${token}`,
5025
+ "Content-Type": "application/json",
5026
+ Accept: "application/vnd.github+json",
5027
+ "X-GitHub-Api-Version": "2022-11-28"
4861
5028
  },
4862
5029
  body: JSON.stringify({
4863
- model: this.modelInfo.model,
5030
+ model: `openai/${this.modelInfo.model}`,
4864
5031
  input: texts
4865
5032
  })
4866
5033
  });
4867
5034
  if (!response.ok) {
4868
- const error = await response.text();
4869
- throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5035
+ const error = (await response.text()).slice(0, 500);
5036
+ throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
4870
5037
  }
4871
5038
  const data = await response.json();
4872
5039
  return {
@@ -4874,16 +5041,14 @@ var OpenAIEmbeddingProvider = class {
4874
5041
  totalTokensUsed: data.usage.total_tokens
4875
5042
  };
4876
5043
  }
4877
- getModelInfo() {
4878
- return this.modelInfo;
4879
- }
4880
5044
  };
4881
- var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
5045
+
5046
+ // src/embeddings/providers/google.ts
5047
+ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
5048
+ static BATCH_SIZE = 20;
4882
5049
  constructor(credentials, modelInfo) {
4883
- this.credentials = credentials;
4884
- this.modelInfo = modelInfo;
5050
+ super(credentials, modelInfo);
4885
5051
  }
4886
- static BATCH_SIZE = 20;
4887
5052
  async embedQuery(query) {
4888
5053
  const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
4889
5054
  const result = await this.embedWithTaskType([query], taskType);
@@ -4904,12 +5069,6 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4904
5069
  const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
4905
5070
  return this.embedWithTaskType(texts, taskType);
4906
5071
  }
4907
- /**
4908
- * Embeds texts using the Google embedContent API.
4909
- * Sends multiple texts as parts in batched requests (up to BATCH_SIZE per call).
4910
- * When taskType is provided (gemini-embedding-001), includes it in the request
4911
- * for task-specific embedding optimization.
4912
- */
4913
5072
  async embedWithTaskType(texts, taskType) {
4914
5073
  const batches = [];
4915
5074
  for (let i = 0; i < texts.length; i += _GoogleEmbeddingProvider.BATCH_SIZE) {
@@ -4926,17 +5085,18 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4926
5085
  outputDimensionality: this.modelInfo.dimensions
4927
5086
  }));
4928
5087
  const response = await fetch(
4929
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents?key=${this.credentials.apiKey}`,
5088
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:batchEmbedContents`,
4930
5089
  {
4931
5090
  method: "POST",
4932
5091
  headers: {
4933
- "Content-Type": "application/json"
5092
+ "Content-Type": "application/json",
5093
+ ...this.credentials.apiKey && { "x-goog-api-key": this.credentials.apiKey }
4934
5094
  },
4935
5095
  body: JSON.stringify({ requests })
4936
5096
  }
4937
5097
  );
4938
5098
  if (!response.ok) {
4939
- const error = await response.text();
5099
+ const error = (await response.text()).slice(0, 500);
4940
5100
  throw new Error(`Google embedding API error: ${response.status} - ${error}`);
4941
5101
  }
4942
5102
  const data = await response.json();
@@ -4951,29 +5111,13 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4951
5111
  totalTokensUsed: batchResults.reduce((sum, r) => sum + r.tokensUsed, 0)
4952
5112
  };
4953
5113
  }
4954
- getModelInfo() {
4955
- return this.modelInfo;
4956
- }
4957
5114
  };
4958
- var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4959
- constructor(credentials, modelInfo) {
4960
- this.credentials = credentials;
4961
- this.modelInfo = modelInfo;
4962
- }
5115
+
5116
+ // src/embeddings/providers/ollama.ts
5117
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
4963
5118
  static MIN_TRUNCATION_CHARS = 512;
4964
- async embedQuery(query) {
4965
- const result = await this.embedBatch([query]);
4966
- return {
4967
- embedding: result.embeddings[0],
4968
- tokensUsed: result.totalTokensUsed
4969
- };
4970
- }
4971
- async embedDocument(document) {
4972
- const result = await this.embedBatch([document]);
4973
- return {
4974
- embedding: result.embeddings[0],
4975
- tokensUsed: result.totalTokensUsed
4976
- };
5119
+ constructor(credentials, modelInfo) {
5120
+ super(credentials, modelInfo);
4977
5121
  }
4978
5122
  estimateTokens(text) {
4979
5123
  return Math.ceil(text.length / 4);
@@ -5037,166 +5181,97 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
5037
5181
  return await this.embedSingle(truncated);
5038
5182
  } catch (retryError) {
5039
5183
  if (!this.isContextLengthError(retryError)) {
5040
- throw retryError;
5041
- }
5042
- lastError = retryError;
5043
- }
5044
- }
5045
- throw lastError;
5046
- }
5047
- }
5048
- async embedSingle(text) {
5049
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5050
- method: "POST",
5051
- headers: {
5052
- "Content-Type": "application/json"
5053
- },
5054
- body: JSON.stringify({
5055
- model: this.modelInfo.model,
5056
- prompt: text,
5057
- truncate: false
5058
- })
5059
- });
5060
- if (!response.ok) {
5061
- const error = await response.text();
5062
- throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5063
- }
5064
- const data = await response.json();
5065
- return {
5066
- embedding: data.embedding,
5067
- tokensUsed: this.estimateTokens(text)
5068
- };
5069
- }
5070
- async embedBatch(texts) {
5071
- const results = [];
5072
- for (const text of texts) {
5073
- results.push(await this.embedSingleWithFallback(text));
5074
- }
5075
- return {
5076
- embeddings: results.map((r) => r.embedding),
5077
- totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5078
- };
5079
- }
5080
- getModelInfo() {
5081
- return this.modelInfo;
5082
- }
5083
- };
5084
- var CustomEmbeddingProvider = class {
5085
- constructor(credentials, modelInfo) {
5086
- this.credentials = credentials;
5087
- this.modelInfo = modelInfo;
5088
- }
5089
- splitIntoRequestBatches(texts) {
5090
- const maxBatchSize = this.modelInfo.maxBatchSize;
5091
- if (!maxBatchSize || texts.length <= maxBatchSize) {
5092
- return [texts];
5093
- }
5094
- const batches = [];
5095
- for (let i = 0; i < texts.length; i += maxBatchSize) {
5096
- batches.push(texts.slice(i, i + maxBatchSize));
5097
- }
5098
- return batches;
5099
- }
5100
- async embedRequest(texts) {
5101
- if (texts.length === 0) {
5102
- return {
5103
- embeddings: [],
5104
- totalTokensUsed: 0
5105
- };
5106
- }
5107
- const headers = {
5108
- "Content-Type": "application/json"
5109
- };
5110
- if (this.credentials.apiKey) {
5111
- headers["Authorization"] = `Bearer ${this.credentials.apiKey}`;
5112
- }
5113
- const baseUrl = this.credentials.baseUrl ?? "";
5114
- const timeoutMs = this.modelInfo.timeoutMs;
5115
- const controller = new AbortController();
5116
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
5117
- let response;
5118
- try {
5119
- response = await fetch(`${baseUrl}/embeddings`, {
5120
- method: "POST",
5121
- headers,
5122
- body: JSON.stringify({
5123
- model: this.modelInfo.model,
5124
- input: texts
5125
- }),
5126
- signal: controller.signal
5127
- });
5128
- } catch (error) {
5129
- if (error instanceof Error && error.name === "AbortError") {
5130
- throw new Error(`Custom embedding API request timed out after ${timeoutMs}ms for ${baseUrl}/embeddings`);
5131
- }
5132
- throw error;
5133
- } finally {
5134
- clearTimeout(timeout);
5135
- }
5136
- if (!response.ok) {
5137
- const errorText = await response.text();
5138
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
5139
- throw new CustomProviderNonRetryableError(`Custom embedding API error (non-retryable): ${response.status} - ${errorText}`);
5140
- }
5141
- throw new Error(`Custom embedding API error: ${response.status} - ${errorText}`);
5142
- }
5143
- const data = await response.json();
5144
- if (data.data && Array.isArray(data.data)) {
5145
- if (data.data.length > 0) {
5146
- const actualDims = data.data[0].embedding.length;
5147
- if (actualDims !== this.modelInfo.dimensions) {
5148
- throw new Error(
5149
- `Dimension mismatch: customProvider.dimensions is ${this.modelInfo.dimensions}, but the API returned vectors with ${actualDims} dimensions. Update your config to match the model's actual output dimensions.`
5150
- );
5184
+ throw retryError;
5185
+ }
5186
+ lastError = retryError;
5151
5187
  }
5152
5188
  }
5153
- if (data.data.length !== texts.length) {
5154
- throw new Error(
5155
- `Embedding count mismatch: sent ${texts.length} texts but received ${data.data.length} embeddings. The custom embedding server may not support batch input.`
5156
- );
5157
- }
5158
- return {
5159
- embeddings: data.data.map((d) => d.embedding),
5160
- // Rough estimate: ~4 chars per token. Used as fallback when the server
5161
- // doesn't return usage.total_tokens (e.g. llama.cpp, some vLLM configs).
5162
- totalTokensUsed: data.usage?.total_tokens ?? texts.reduce((sum, t) => sum + Math.ceil(t.length / 4), 0)
5163
- };
5189
+ throw lastError;
5164
5190
  }
5165
- throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
5166
5191
  }
5167
- async embedQuery(query) {
5168
- const result = await this.embedBatch([query]);
5192
+ async embedSingle(text) {
5193
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5194
+ method: "POST",
5195
+ headers: {
5196
+ "Content-Type": "application/json"
5197
+ },
5198
+ body: JSON.stringify({
5199
+ model: this.modelInfo.model,
5200
+ prompt: text,
5201
+ truncate: false
5202
+ })
5203
+ });
5204
+ if (!response.ok) {
5205
+ const error = (await response.text()).slice(0, 500);
5206
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5207
+ }
5208
+ const data = await response.json();
5169
5209
  return {
5170
- embedding: result.embeddings[0],
5171
- tokensUsed: result.totalTokensUsed
5210
+ embedding: data.embedding,
5211
+ tokensUsed: this.estimateTokens(text)
5172
5212
  };
5173
5213
  }
5174
- async embedDocument(document) {
5175
- const result = await this.embedBatch([document]);
5214
+ async embedBatch(texts) {
5215
+ const results = [];
5216
+ for (const text of texts) {
5217
+ results.push(await this.embedSingleWithFallback(text));
5218
+ }
5176
5219
  return {
5177
- embedding: result.embeddings[0],
5178
- tokensUsed: result.totalTokensUsed
5220
+ embeddings: results.map((r) => r.embedding),
5221
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
5179
5222
  };
5180
5223
  }
5224
+ };
5225
+
5226
+ // src/embeddings/providers/openai.ts
5227
+ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
5228
+ constructor(credentials, modelInfo) {
5229
+ super(credentials, modelInfo);
5230
+ }
5181
5231
  async embedBatch(texts) {
5182
- const requestBatches = this.splitIntoRequestBatches(texts);
5183
- const embeddings = [];
5184
- let totalTokensUsed = 0;
5185
- for (const batch of requestBatches) {
5186
- const result = await this.embedRequest(batch);
5187
- embeddings.push(...result.embeddings);
5188
- totalTokensUsed += result.totalTokensUsed;
5232
+ const response = await fetch(`${this.credentials.baseUrl}/embeddings`, {
5233
+ method: "POST",
5234
+ headers: {
5235
+ Authorization: `Bearer ${this.credentials.apiKey}`,
5236
+ "Content-Type": "application/json"
5237
+ },
5238
+ body: JSON.stringify({
5239
+ model: this.modelInfo.model,
5240
+ input: texts
5241
+ })
5242
+ });
5243
+ if (!response.ok) {
5244
+ const error = (await response.text()).slice(0, 500);
5245
+ throw new Error(`OpenAI embedding API error: ${response.status} - ${error}`);
5189
5246
  }
5247
+ const data = await response.json();
5190
5248
  return {
5191
- embeddings,
5192
- totalTokensUsed
5249
+ embeddings: data.data.map((d) => d.embedding),
5250
+ totalTokensUsed: data.usage.total_tokens
5193
5251
  };
5194
5252
  }
5195
- getModelInfo() {
5196
- return this.modelInfo;
5197
- }
5198
5253
  };
5199
5254
 
5255
+ // src/embeddings/provider.ts
5256
+ function createEmbeddingProvider(configuredProviderInfo) {
5257
+ switch (configuredProviderInfo.provider) {
5258
+ case "github-copilot":
5259
+ return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5260
+ case "openai":
5261
+ return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5262
+ case "google":
5263
+ return new GoogleEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5264
+ case "ollama":
5265
+ return new OllamaEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5266
+ case "custom":
5267
+ return new CustomEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
5268
+ default: {
5269
+ const _exhaustive = configuredProviderInfo;
5270
+ throw new Error(`Unsupported embedding provider: ${_exhaustive.provider}`);
5271
+ }
5272
+ }
5273
+ }
5274
+
5200
5275
  // src/rerank/index.ts
5201
5276
  function createReranker(config) {
5202
5277
  if (!config.enabled) {
@@ -5232,7 +5307,10 @@ var SiliconFlowReranker = class {
5232
5307
  if (this.config.apiKey) {
5233
5308
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
5234
5309
  }
5235
- const baseUrl = this.config.baseUrl ?? "https://api.siliconflow.cn/v1";
5310
+ const baseUrl = this.config.baseUrl;
5311
+ if (!baseUrl) {
5312
+ throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
5313
+ }
5236
5314
  const timeoutMs = this.config.timeoutMs ?? 3e4;
5237
5315
  const controller = new AbortController();
5238
5316
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -5306,20 +5384,21 @@ function createCostEstimate(files, provider) {
5306
5384
  }
5307
5385
  function formatCostEstimate(estimate) {
5308
5386
  const sizeFormatted = formatBytes(estimate.totalSizeBytes);
5387
+ const filesFormatted = `${estimate.filesCount.toLocaleString()} files`;
5309
5388
  const costFormatted = estimate.isFree ? "Free" : `~$${estimate.estimatedCost.toFixed(4)}`;
5310
5389
  return `
5311
5390
  \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5312
5391
  \u2502 \u{1F4CA} Indexing Estimate \u2502
5313
5392
  \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
5314
5393
  \u2502 \u2502
5315
- \u2502 Files to index: ${padRight(estimate.filesCount.toLocaleString() + " files", 40)}\u2502
5316
- \u2502 Total size: ${padRight(sizeFormatted, 40)}\u2502
5317
- \u2502 Estimated chunks: ${padRight("~" + estimate.estimatedChunks.toLocaleString() + " chunks", 40)}\u2502
5318
- \u2502 Estimated tokens: ${padRight("~" + estimate.estimatedTokens.toLocaleString() + " tokens", 40)}\u2502
5394
+ \u2502 Files to index: ${filesFormatted.padEnd(40)}\u2502
5395
+ \u2502 Total size: ${sizeFormatted.padEnd(40)}\u2502
5396
+ \u2502 Estimated chunks: ${("~" + estimate.estimatedChunks.toLocaleString() + " chunks").padEnd(40)}\u2502
5397
+ \u2502 Estimated tokens: ${("~" + estimate.estimatedTokens.toLocaleString() + " tokens").padEnd(40)}\u2502
5319
5398
  \u2502 \u2502
5320
- \u2502 Provider: ${padRight(estimate.provider, 52)}\u2502
5321
- \u2502 Model: ${padRight(estimate.model, 52)}\u2502
5322
- \u2502 Cost: ${padRight(costFormatted, 52)}\u2502
5399
+ \u2502 Provider: ${estimate.provider.padEnd(52)}\u2502
5400
+ \u2502 Model: ${estimate.model.padEnd(52)}\u2502
5401
+ \u2502 Cost: ${costFormatted.padEnd(52)}\u2502
5323
5402
  \u2502 \u2502
5324
5403
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
5325
5404
  `;
@@ -5331,9 +5410,6 @@ function formatBytes(bytes) {
5331
5410
  const i = Math.floor(Math.log(bytes) / Math.log(k));
5332
5411
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
5333
5412
  }
5334
- function padRight(str, length) {
5335
- return str.padEnd(length);
5336
- }
5337
5413
 
5338
5414
  // src/utils/logger.ts
5339
5415
  var LOG_LEVEL_PRIORITY = {
@@ -5400,6 +5476,10 @@ var Logger = class {
5400
5476
  this.logs.shift();
5401
5477
  }
5402
5478
  }
5479
+ withMetrics(fn) {
5480
+ if (!this.config.metrics) return;
5481
+ fn();
5482
+ }
5403
5483
  search(level, message, data) {
5404
5484
  if (this.config.logSearch) {
5405
5485
  this.log(level, "search", message, data);
@@ -5438,89 +5518,107 @@ var Logger = class {
5438
5518
  this.log("debug", "general", message, data);
5439
5519
  }
5440
5520
  recordIndexingStart() {
5441
- if (!this.config.metrics) return;
5442
- this.metrics.indexingStartTime = Date.now();
5521
+ this.withMetrics(() => {
5522
+ this.metrics.indexingStartTime = Date.now();
5523
+ });
5443
5524
  }
5444
5525
  recordIndexingEnd() {
5445
- if (!this.config.metrics) return;
5446
- this.metrics.indexingEndTime = Date.now();
5526
+ this.withMetrics(() => {
5527
+ this.metrics.indexingEndTime = Date.now();
5528
+ });
5447
5529
  }
5448
5530
  recordFilesScanned(count) {
5449
- if (!this.config.metrics) return;
5450
- this.metrics.filesScanned = count;
5531
+ this.withMetrics(() => {
5532
+ this.metrics.filesScanned = count;
5533
+ });
5451
5534
  }
5452
5535
  recordFilesParsed(count) {
5453
- if (!this.config.metrics) return;
5454
- this.metrics.filesParsed = count;
5536
+ this.withMetrics(() => {
5537
+ this.metrics.filesParsed = count;
5538
+ });
5455
5539
  }
5456
5540
  recordParseDuration(durationMs) {
5457
- if (!this.config.metrics) return;
5458
- this.metrics.parseMs = durationMs;
5541
+ this.withMetrics(() => {
5542
+ this.metrics.parseMs = durationMs;
5543
+ });
5459
5544
  }
5460
5545
  recordChunksProcessed(count) {
5461
- if (!this.config.metrics) return;
5462
- this.metrics.chunksProcessed += count;
5546
+ this.withMetrics(() => {
5547
+ this.metrics.chunksProcessed += count;
5548
+ });
5463
5549
  }
5464
5550
  recordChunksEmbedded(count) {
5465
- if (!this.config.metrics) return;
5466
- this.metrics.chunksEmbedded += count;
5551
+ this.withMetrics(() => {
5552
+ this.metrics.chunksEmbedded += count;
5553
+ });
5467
5554
  }
5468
5555
  recordChunksFromCache(count) {
5469
- if (!this.config.metrics) return;
5470
- this.metrics.chunksFromCache += count;
5556
+ this.withMetrics(() => {
5557
+ this.metrics.chunksFromCache += count;
5558
+ });
5471
5559
  }
5472
5560
  recordChunksRemoved(count) {
5473
- if (!this.config.metrics) return;
5474
- this.metrics.chunksRemoved += count;
5561
+ this.withMetrics(() => {
5562
+ this.metrics.chunksRemoved += count;
5563
+ });
5475
5564
  }
5476
5565
  recordEmbeddingApiCall(tokens) {
5477
- if (!this.config.metrics) return;
5478
- this.metrics.embeddingApiCalls++;
5479
- this.metrics.embeddingTokensUsed += tokens;
5566
+ this.withMetrics(() => {
5567
+ this.metrics.embeddingApiCalls++;
5568
+ this.metrics.embeddingTokensUsed += tokens;
5569
+ });
5480
5570
  }
5481
5571
  recordEmbeddingError() {
5482
- if (!this.config.metrics) return;
5483
- this.metrics.embeddingErrors++;
5572
+ this.withMetrics(() => {
5573
+ this.metrics.embeddingErrors++;
5574
+ });
5484
5575
  }
5485
5576
  recordSearch(durationMs, breakdown) {
5486
- if (!this.config.metrics) return;
5487
- this.metrics.searchCount++;
5488
- this.metrics.searchTotalMs += durationMs;
5489
- this.metrics.searchLastMs = durationMs;
5490
- this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5491
- if (breakdown) {
5492
- this.metrics.embeddingCallMs = breakdown.embeddingMs;
5493
- this.metrics.vectorSearchMs = breakdown.vectorMs;
5494
- this.metrics.keywordSearchMs = breakdown.keywordMs;
5495
- this.metrics.fusionMs = breakdown.fusionMs;
5496
- }
5577
+ this.withMetrics(() => {
5578
+ this.metrics.searchCount++;
5579
+ this.metrics.searchTotalMs += durationMs;
5580
+ this.metrics.searchLastMs = durationMs;
5581
+ this.metrics.searchAvgMs = this.metrics.searchTotalMs / this.metrics.searchCount;
5582
+ if (breakdown) {
5583
+ this.metrics.embeddingCallMs = breakdown.embeddingMs;
5584
+ this.metrics.vectorSearchMs = breakdown.vectorMs;
5585
+ this.metrics.keywordSearchMs = breakdown.keywordMs;
5586
+ this.metrics.fusionMs = breakdown.fusionMs;
5587
+ }
5588
+ });
5497
5589
  }
5498
5590
  recordCacheHit() {
5499
- if (!this.config.metrics) return;
5500
- this.metrics.cacheHits++;
5591
+ this.withMetrics(() => {
5592
+ this.metrics.cacheHits++;
5593
+ });
5501
5594
  }
5502
5595
  recordCacheMiss() {
5503
- if (!this.config.metrics) return;
5504
- this.metrics.cacheMisses++;
5596
+ this.withMetrics(() => {
5597
+ this.metrics.cacheMisses++;
5598
+ });
5505
5599
  }
5506
5600
  recordQueryCacheHit() {
5507
- if (!this.config.metrics) return;
5508
- this.metrics.queryCacheHits++;
5601
+ this.withMetrics(() => {
5602
+ this.metrics.queryCacheHits++;
5603
+ });
5509
5604
  }
5510
5605
  recordQueryCacheSimilarHit() {
5511
- if (!this.config.metrics) return;
5512
- this.metrics.queryCacheSimilarHits++;
5606
+ this.withMetrics(() => {
5607
+ this.metrics.queryCacheSimilarHits++;
5608
+ });
5513
5609
  }
5514
5610
  recordQueryCacheMiss() {
5515
- if (!this.config.metrics) return;
5516
- this.metrics.queryCacheMisses++;
5611
+ this.withMetrics(() => {
5612
+ this.metrics.queryCacheMisses++;
5613
+ });
5517
5614
  }
5518
5615
  recordGc(orphans, chunks, embeddings) {
5519
- if (!this.config.metrics) return;
5520
- this.metrics.gcRuns++;
5521
- this.metrics.gcOrphansRemoved += orphans;
5522
- this.metrics.gcChunksRemoved += chunks;
5523
- this.metrics.gcEmbeddingsRemoved += embeddings;
5616
+ this.withMetrics(() => {
5617
+ this.metrics.gcRuns++;
5618
+ this.metrics.gcOrphansRemoved += orphans;
5619
+ this.metrics.gcChunksRemoved += chunks;
5620
+ this.metrics.gcEmbeddingsRemoved += embeddings;
5621
+ });
5524
5622
  }
5525
5623
  getMetrics() {
5526
5624
  return { ...this.metrics };
@@ -5627,7 +5725,7 @@ function initializeLogger(config) {
5627
5725
  }
5628
5726
 
5629
5727
  // src/native/index.ts
5630
- var path7 = __toESM(require("path"), 1);
5728
+ var path11 = __toESM(require("path"), 1);
5631
5729
  var os4 = __toESM(require("os"), 1);
5632
5730
  var module2 = __toESM(require("module"), 1);
5633
5731
  var import_url = require("url");
@@ -5652,19 +5750,19 @@ function getNativeBinding() {
5652
5750
  let currentDir;
5653
5751
  let requireTarget;
5654
5752
  if (typeof import_meta !== "undefined" && import_meta.url) {
5655
- currentDir = path7.dirname((0, import_url.fileURLToPath)(import_meta.url));
5753
+ currentDir = path11.dirname((0, import_url.fileURLToPath)(import_meta.url));
5656
5754
  requireTarget = import_meta.url;
5657
5755
  } else if (typeof __dirname !== "undefined") {
5658
5756
  currentDir = __dirname;
5659
5757
  requireTarget = __filename;
5660
5758
  } else {
5661
5759
  currentDir = process.cwd();
5662
- requireTarget = path7.join(currentDir, "index.js");
5760
+ requireTarget = path11.join(currentDir, "index.js");
5663
5761
  }
5664
5762
  const normalizedDir = currentDir.replace(/\\/g, "/");
5665
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
5666
- const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
5667
- const nativePath = path7.join(packageRoot, "native", bindingName);
5763
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path11.join("src", "native"));
5764
+ const packageRoot = isDevMode ? path11.resolve(currentDir, "../..") : path11.resolve(currentDir, "..");
5765
+ const nativePath = path11.join(packageRoot, "native", bindingName);
5668
5766
  const require2 = module2.createRequire(requireTarget);
5669
5767
  return require2(nativePath);
5670
5768
  }
@@ -6234,7 +6332,6 @@ var Database = class {
6234
6332
  this.throwIfClosed();
6235
6333
  return this.inner.getStats();
6236
6334
  }
6237
- // ── Symbol methods ──────────────────────────────────────────────
6238
6335
  upsertSymbol(symbol) {
6239
6336
  this.throwIfClosed();
6240
6337
  this.inner.upsertSymbol(symbol);
@@ -6264,7 +6361,6 @@ var Database = class {
6264
6361
  this.throwIfClosed();
6265
6362
  return this.inner.deleteSymbolsByFile(filePath);
6266
6363
  }
6267
- // ── Call Edge methods ────────────────────────────────────────────
6268
6364
  upsertCallEdge(edge) {
6269
6365
  this.throwIfClosed();
6270
6366
  this.inner.upsertCallEdge(edge);
@@ -6294,7 +6390,6 @@ var Database = class {
6294
6390
  this.throwIfClosed();
6295
6391
  this.inner.resolveCallEdge(edgeId, toSymbolId);
6296
6392
  }
6297
- // ── Branch Symbol methods ────────────────────────────────────────
6298
6393
  addSymbolsToBranch(branch, symbolIds) {
6299
6394
  this.throwIfClosed();
6300
6395
  this.inner.addSymbolsToBranch(branch, symbolIds);
@@ -6327,7 +6422,6 @@ var Database = class {
6327
6422
  if (symbolIds.length === 0) return 0;
6328
6423
  return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6329
6424
  }
6330
- // ── GC methods for symbols/edges ─────────────────────────────────
6331
6425
  gcOrphanSymbols() {
6332
6426
  this.throwIfClosed();
6333
6427
  return this.inner.gcOrphanSymbols();
@@ -6339,7 +6433,7 @@ var Database = class {
6339
6433
  };
6340
6434
 
6341
6435
  // src/indexer/index.ts
6342
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6436
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
6343
6437
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
6344
6438
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6345
6439
  "function_declaration",
@@ -6366,7 +6460,15 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6366
6460
  "trigger_declaration",
6367
6461
  "test_declaration",
6368
6462
  "struct_declaration",
6369
- "union_declaration"
6463
+ "union_declaration",
6464
+ // GDScript declarations whose names participate in the call graph.
6465
+ // `function_definition` and `class_definition` are already in the set
6466
+ // above (shared with Python/C/Bash and Python, respectively).
6467
+ "constructor_definition",
6468
+ "enum_definition",
6469
+ "signal_statement",
6470
+ "const_statement",
6471
+ "class_name_statement"
6370
6472
  ]);
6371
6473
  function float32ArrayToBuffer(arr) {
6372
6474
  const float32 = new Float32Array(arr);
@@ -6568,9 +6670,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
6568
6670
  return true;
6569
6671
  }
6570
6672
  function isPathWithinRoot(filePath, rootPath) {
6571
- const normalizedFilePath = path8.resolve(filePath);
6572
- const normalizedRoot = path8.resolve(rootPath);
6573
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path8.sep}`);
6673
+ const normalizedFilePath = path12.resolve(filePath);
6674
+ const normalizedRoot = path12.resolve(rootPath);
6675
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
6574
6676
  }
6575
6677
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6576
6678
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -7623,22 +7725,28 @@ var Indexer = class {
7623
7725
  this.projectRoot = projectRoot;
7624
7726
  this.config = config;
7625
7727
  this.indexPath = this.getIndexPath();
7626
- this.fileHashCachePath = path8.join(this.indexPath, "file-hashes.json");
7627
- this.failedBatchesPath = path8.join(this.indexPath, "failed-batches.json");
7628
- this.indexingLockPath = path8.join(this.indexPath, "indexing.lock");
7728
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
7729
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
7730
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
7629
7731
  this.logger = initializeLogger(config.debug);
7630
7732
  }
7631
7733
  getIndexPath() {
7632
7734
  return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7633
7735
  }
7634
7736
  loadFileHashCache() {
7737
+ if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
7738
+ return;
7739
+ }
7635
7740
  try {
7636
- if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
7637
- const data = (0, import_fs6.readFileSync)(this.fileHashCachePath, "utf-8");
7638
- const parsed = JSON.parse(data);
7639
- this.fileHashCache = new Map(Object.entries(parsed));
7640
- }
7641
- } catch {
7741
+ const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
7742
+ const parsed = JSON.parse(data);
7743
+ this.fileHashCache = new Map(Object.entries(parsed));
7744
+ } catch (error) {
7745
+ const message = error instanceof Error ? error.message : String(error);
7746
+ this.logger.warn("Failed to load file hash cache, resetting cache state", {
7747
+ fileHashCachePath: this.fileHashCachePath,
7748
+ error: message
7749
+ });
7642
7750
  this.fileHashCache = /* @__PURE__ */ new Map();
7643
7751
  }
7644
7752
  }
@@ -7651,14 +7759,14 @@ var Indexer = class {
7651
7759
  }
7652
7760
  atomicWriteSync(targetPath, data) {
7653
7761
  const tempPath = `${targetPath}.tmp`;
7654
- (0, import_fs6.mkdirSync)(path8.dirname(targetPath), { recursive: true });
7655
- (0, import_fs6.writeFileSync)(tempPath, data);
7656
- (0, import_fs6.renameSync)(tempPath, targetPath);
7762
+ (0, import_fs7.mkdirSync)(path12.dirname(targetPath), { recursive: true });
7763
+ (0, import_fs7.writeFileSync)(tempPath, data);
7764
+ (0, import_fs7.renameSync)(tempPath, targetPath);
7657
7765
  }
7658
7766
  getScopedRoots() {
7659
- const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7767
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7660
7768
  for (const kbRoot of this.config.knowledgeBases) {
7661
- roots.add(path8.resolve(this.projectRoot, kbRoot));
7769
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
7662
7770
  }
7663
7771
  return Array.from(roots);
7664
7772
  }
@@ -7667,22 +7775,22 @@ var Indexer = class {
7667
7775
  if (this.config.scope !== "global") {
7668
7776
  return branchName;
7669
7777
  }
7670
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7778
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7671
7779
  return `${projectHash}:${branchName}`;
7672
7780
  }
7673
7781
  getLegacyBranchCatalogKey() {
7674
7782
  return this.currentBranch || "default";
7675
7783
  }
7676
7784
  getLegacyMigrationMetadataKey() {
7677
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7785
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7678
7786
  return `index.globalBranchMigration.${projectHash}`;
7679
7787
  }
7680
7788
  getProjectEmbeddingStrategyMetadataKey() {
7681
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7789
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7682
7790
  return `index.embeddingStrategyVersion.${projectHash}`;
7683
7791
  }
7684
7792
  getProjectForceReembedMetadataKey() {
7685
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7793
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7686
7794
  return `index.forceReembed.${projectHash}`;
7687
7795
  }
7688
7796
  hasProjectForceReembedPending() {
@@ -7776,7 +7884,7 @@ var Indexer = class {
7776
7884
  if (!this.database) {
7777
7885
  return { chunkIds, symbolIds };
7778
7886
  }
7779
- const projectRootPath = path8.resolve(this.projectRoot);
7887
+ const projectRootPath = path12.resolve(this.projectRoot);
7780
7888
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7781
7889
  ...Array.from(this.fileHashCache.keys()).filter(
7782
7890
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7799,7 +7907,7 @@ var Indexer = class {
7799
7907
  if (this.config.scope !== "global") {
7800
7908
  return this.getBranchCatalogCleanupKeys();
7801
7909
  }
7802
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7910
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7803
7911
  const keys = /* @__PURE__ */ new Set();
7804
7912
  const projectChunkIdSet = new Set(projectChunkIds);
7805
7913
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7883,7 +7991,7 @@ var Indexer = class {
7883
7991
  if (!this.database || this.config.scope !== "global") {
7884
7992
  return false;
7885
7993
  }
7886
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7994
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7887
7995
  const roots = this.getScopedRoots();
7888
7996
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7889
7997
  return this.database.getAllBranches().some(
@@ -7917,7 +8025,7 @@ var Indexer = class {
7917
8025
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7918
8026
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7919
8027
  ]);
7920
- const projectRootPath = path8.resolve(this.projectRoot);
8028
+ const projectRootPath = path12.resolve(this.projectRoot);
7921
8029
  const projectLocalFilePaths = new Set(
7922
8030
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7923
8031
  );
@@ -7986,24 +8094,24 @@ var Indexer = class {
7986
8094
  };
7987
8095
  }
7988
8096
  checkForInterruptedIndexing() {
7989
- return (0, import_fs6.existsSync)(this.indexingLockPath);
8097
+ return (0, import_fs7.existsSync)(this.indexingLockPath);
7990
8098
  }
7991
8099
  acquireIndexingLock() {
7992
8100
  const lockData = {
7993
8101
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7994
8102
  pid: process.pid
7995
8103
  };
7996
- (0, import_fs6.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
8104
+ (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
7997
8105
  }
7998
8106
  releaseIndexingLock() {
7999
- if ((0, import_fs6.existsSync)(this.indexingLockPath)) {
8000
- (0, import_fs6.unlinkSync)(this.indexingLockPath);
8107
+ if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
8108
+ (0, import_fs7.unlinkSync)(this.indexingLockPath);
8001
8109
  }
8002
8110
  }
8003
8111
  async recoverFromInterruptedIndexing() {
8004
8112
  this.logger.warn("Detected interrupted indexing session, recovering...");
8005
- if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
8006
- (0, import_fs6.unlinkSync)(this.fileHashCachePath);
8113
+ if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
8114
+ (0, import_fs7.unlinkSync)(this.fileHashCachePath);
8007
8115
  }
8008
8116
  await this.healthCheck();
8009
8117
  this.releaseIndexingLock();
@@ -8012,15 +8120,20 @@ var Indexer = class {
8012
8120
  loadFailedBatches(maxChunkTokens) {
8013
8121
  try {
8014
8122
  return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8015
- } catch {
8123
+ } catch (error) {
8124
+ const message = error instanceof Error ? error.message : String(error);
8125
+ this.logger.warn("Failed to load failed batch state, skipping persisted retries", {
8126
+ failedBatchesPath: this.failedBatchesPath,
8127
+ error: message
8128
+ });
8016
8129
  return [];
8017
8130
  }
8018
8131
  }
8019
8132
  loadSerializedFailedBatches() {
8020
- if (!(0, import_fs6.existsSync)(this.failedBatchesPath)) {
8133
+ if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
8021
8134
  return [];
8022
8135
  }
8023
- const data = (0, import_fs6.readFileSync)(this.failedBatchesPath, "utf-8");
8136
+ const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
8024
8137
  const parsed = JSON.parse(data);
8025
8138
  return parsed.map((batch) => {
8026
8139
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -8037,15 +8150,15 @@ var Indexer = class {
8037
8150
  }
8038
8151
  saveFailedBatches(batches) {
8039
8152
  if (batches.length === 0) {
8040
- if ((0, import_fs6.existsSync)(this.failedBatchesPath)) {
8153
+ if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
8041
8154
  try {
8042
- (0, import_fs6.unlinkSync)(this.failedBatchesPath);
8155
+ (0, import_fs7.unlinkSync)(this.failedBatchesPath);
8043
8156
  } catch {
8044
8157
  }
8045
8158
  }
8046
8159
  return;
8047
8160
  }
8048
- (0, import_fs6.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8161
+ (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8049
8162
  }
8050
8163
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8051
8164
  const retryableById = /* @__PURE__ */ new Map();
@@ -8225,7 +8338,7 @@ var Indexer = class {
8225
8338
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
8226
8339
  parts.push(`intent_hint: ${intent}`);
8227
8340
  try {
8228
- const fileContent = await import_fs6.promises.readFile(candidate.metadata.filePath, "utf-8");
8341
+ const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
8229
8342
  const lines = fileContent.split("\n");
8230
8343
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
8231
8344
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -8270,26 +8383,26 @@ var Indexer = class {
8270
8383
  });
8271
8384
  }
8272
8385
  }
8273
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8386
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8274
8387
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8275
- const storePath = path8.join(this.indexPath, "vectors");
8388
+ const storePath = path12.join(this.indexPath, "vectors");
8276
8389
  this.store = new VectorStore(storePath, dimensions);
8277
- const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8278
- if ((0, import_fs6.existsSync)(indexFilePath)) {
8390
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8391
+ if ((0, import_fs7.existsSync)(indexFilePath)) {
8279
8392
  this.store.load();
8280
8393
  }
8281
- const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
8394
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8282
8395
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8283
8396
  try {
8284
8397
  this.invertedIndex.load();
8285
8398
  } catch {
8286
- if ((0, import_fs6.existsSync)(invertedIndexPath)) {
8287
- await import_fs6.promises.unlink(invertedIndexPath);
8399
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
8400
+ await import_fs7.promises.unlink(invertedIndexPath);
8288
8401
  }
8289
8402
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8290
8403
  }
8291
- const dbPath = path8.join(this.indexPath, "codebase.db");
8292
- let dbIsNew = !(0, import_fs6.existsSync)(dbPath);
8404
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8405
+ let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
8293
8406
  try {
8294
8407
  this.database = new Database(dbPath);
8295
8408
  } catch (error) {
@@ -8369,7 +8482,7 @@ var Indexer = class {
8369
8482
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8370
8483
  return {
8371
8484
  resetCorruptedIndex: true,
8372
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8485
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8373
8486
  };
8374
8487
  }
8375
8488
  throw error;
@@ -8384,7 +8497,7 @@ var Indexer = class {
8384
8497
  return;
8385
8498
  }
8386
8499
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8387
- const storeBasePath = path8.join(this.indexPath, "vectors");
8500
+ const storeBasePath = path12.join(this.indexPath, "vectors");
8388
8501
  const storeIndexPath = `${storeBasePath}.usearch`;
8389
8502
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8390
8503
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8393,19 +8506,19 @@ var Indexer = class {
8393
8506
  let backedUpMetadata = false;
8394
8507
  let rebuiltCount = 0;
8395
8508
  let skippedCount = 0;
8396
- if ((0, import_fs6.existsSync)(backupIndexPath)) {
8397
- (0, import_fs6.unlinkSync)(backupIndexPath);
8509
+ if ((0, import_fs7.existsSync)(backupIndexPath)) {
8510
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8398
8511
  }
8399
- if ((0, import_fs6.existsSync)(backupMetadataPath)) {
8400
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8512
+ if ((0, import_fs7.existsSync)(backupMetadataPath)) {
8513
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8401
8514
  }
8402
8515
  try {
8403
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8404
- (0, import_fs6.renameSync)(storeIndexPath, backupIndexPath);
8516
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8517
+ (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
8405
8518
  backedUpIndex = true;
8406
8519
  }
8407
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8408
- (0, import_fs6.renameSync)(storeMetadataPath, backupMetadataPath);
8520
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8521
+ (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
8409
8522
  backedUpMetadata = true;
8410
8523
  }
8411
8524
  store.clear();
@@ -8425,11 +8538,11 @@ var Indexer = class {
8425
8538
  rebuiltCount += 1;
8426
8539
  }
8427
8540
  store.save();
8428
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8429
- (0, import_fs6.unlinkSync)(backupIndexPath);
8541
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8542
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8430
8543
  }
8431
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8432
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8544
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8545
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8433
8546
  }
8434
8547
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8435
8548
  excludedChunks: excludedSet.size,
@@ -8441,17 +8554,17 @@ var Indexer = class {
8441
8554
  store.clear();
8442
8555
  } catch {
8443
8556
  }
8444
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8445
- (0, import_fs6.unlinkSync)(storeIndexPath);
8557
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8558
+ (0, import_fs7.unlinkSync)(storeIndexPath);
8446
8559
  }
8447
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8448
- (0, import_fs6.unlinkSync)(storeMetadataPath);
8560
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8561
+ (0, import_fs7.unlinkSync)(storeMetadataPath);
8449
8562
  }
8450
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8451
- (0, import_fs6.renameSync)(backupIndexPath, storeIndexPath);
8563
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8564
+ (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
8452
8565
  }
8453
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8454
- (0, import_fs6.renameSync)(backupMetadataPath, storeMetadataPath);
8566
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8567
+ (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
8455
8568
  }
8456
8569
  if (backedUpIndex || backedUpMetadata) {
8457
8570
  store.load();
@@ -8469,7 +8582,7 @@ var Indexer = class {
8469
8582
  if (!isSqliteCorruptionError(error)) {
8470
8583
  return false;
8471
8584
  }
8472
- const dbPath = path8.join(this.indexPath, "codebase.db");
8585
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8473
8586
  const warning = this.getCorruptedIndexWarning(dbPath);
8474
8587
  const errorMessage = getErrorMessage(error);
8475
8588
  if (this.config.scope === "global") {
@@ -8492,23 +8605,23 @@ var Indexer = class {
8492
8605
  this.indexCompatibility = null;
8493
8606
  this.fileHashCache.clear();
8494
8607
  const resetPaths = [
8495
- path8.join(this.indexPath, "codebase.db"),
8496
- path8.join(this.indexPath, "codebase.db-shm"),
8497
- path8.join(this.indexPath, "codebase.db-wal"),
8498
- path8.join(this.indexPath, "vectors.usearch"),
8499
- path8.join(this.indexPath, "inverted-index.json"),
8500
- path8.join(this.indexPath, "file-hashes.json"),
8501
- path8.join(this.indexPath, "failed-batches.json"),
8502
- path8.join(this.indexPath, "indexing.lock"),
8503
- path8.join(this.indexPath, "vectors")
8608
+ path12.join(this.indexPath, "codebase.db"),
8609
+ path12.join(this.indexPath, "codebase.db-shm"),
8610
+ path12.join(this.indexPath, "codebase.db-wal"),
8611
+ path12.join(this.indexPath, "vectors.usearch"),
8612
+ path12.join(this.indexPath, "inverted-index.json"),
8613
+ path12.join(this.indexPath, "file-hashes.json"),
8614
+ path12.join(this.indexPath, "failed-batches.json"),
8615
+ path12.join(this.indexPath, "indexing.lock"),
8616
+ path12.join(this.indexPath, "vectors")
8504
8617
  ];
8505
8618
  await Promise.all(resetPaths.map(async (targetPath) => {
8506
8619
  try {
8507
- await import_fs6.promises.rm(targetPath, { recursive: true, force: true });
8620
+ await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
8508
8621
  } catch {
8509
8622
  }
8510
8623
  }));
8511
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8624
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8512
8625
  return true;
8513
8626
  }
8514
8627
  migrateFromLegacyIndex() {
@@ -8713,7 +8826,7 @@ var Indexer = class {
8713
8826
  unchangedFilePaths.add(f.path);
8714
8827
  this.logger.recordCacheHit();
8715
8828
  } else {
8716
- const content = await import_fs6.promises.readFile(f.path, "utf-8");
8829
+ const content = await import_fs7.promises.readFile(f.path, "utf-8");
8717
8830
  changedFiles.push({ path: f.path, content, hash: currentHash });
8718
8831
  this.logger.recordCacheMiss();
8719
8832
  }
@@ -8765,7 +8878,7 @@ var Indexer = class {
8765
8878
  for (const parsed of parsedFiles) {
8766
8879
  currentFilePaths.add(parsed.path);
8767
8880
  if (parsed.chunks.length === 0) {
8768
- const relativePath = path8.relative(this.projectRoot, parsed.path);
8881
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
8769
8882
  stats.parseFailures.push(relativePath);
8770
8883
  }
8771
8884
  let fileChunkCount = 0;
@@ -9048,7 +9161,7 @@ var Indexer = class {
9048
9161
  for (const requestBatch of requestBatches) {
9049
9162
  queue.add(async () => {
9050
9163
  if (rateLimitBackoffMs > 0) {
9051
- await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
9164
+ await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9052
9165
  }
9053
9166
  try {
9054
9167
  const result = await pRetry(
@@ -9497,7 +9610,7 @@ var Indexer = class {
9497
9610
  let contextEndLine = r.metadata.endLine;
9498
9611
  if (!metadataOnly && this.config.search.includeContext) {
9499
9612
  try {
9500
- const fileContent = await import_fs6.promises.readFile(
9613
+ const fileContent = await import_fs7.promises.readFile(
9501
9614
  r.metadata.filePath,
9502
9615
  "utf-8"
9503
9616
  );
@@ -9609,8 +9722,8 @@ var Indexer = class {
9609
9722
  this.indexCompatibility = compatibility;
9610
9723
  return;
9611
9724
  }
9612
- const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9613
- if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9725
+ const localProjectIndexPath = path12.join(this.projectRoot, ".opencode", "index");
9726
+ if (path12.resolve(this.indexPath) !== path12.resolve(localProjectIndexPath)) {
9614
9727
  throw new Error(
9615
9728
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9616
9729
  );
@@ -9649,7 +9762,7 @@ var Indexer = class {
9649
9762
  const removedChunkKeys = [];
9650
9763
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9651
9764
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9652
- if (!(0, import_fs6.existsSync)(filePath)) {
9765
+ if (!(0, import_fs7.existsSync)(filePath)) {
9653
9766
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9654
9767
  for (const key of chunkKeys) {
9655
9768
  removedChunkKeys.push(key);
@@ -9698,7 +9811,7 @@ var Indexer = class {
9698
9811
  gcOrphanSymbols: 0,
9699
9812
  gcOrphanCallEdges: 0,
9700
9813
  resetCorruptedIndex: true,
9701
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9814
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9702
9815
  };
9703
9816
  }
9704
9817
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10001,7 +10114,7 @@ var Indexer = class {
10001
10114
  let content = "";
10002
10115
  if (this.config.search.includeContext) {
10003
10116
  try {
10004
- const fileContent = await import_fs6.promises.readFile(
10117
+ const fileContent = await import_fs7.promises.readFile(
10005
10118
  r.metadata.filePath,
10006
10119
  "utf-8"
10007
10120
  );
@@ -10250,12 +10363,15 @@ function formatLogs(logs) {
10250
10363
  return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10251
10364
  }).join("\n");
10252
10365
  }
10366
+ function formatResultHeader(result, index) {
10367
+ 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}`;
10368
+ }
10253
10369
  function formatDefinitionLookup(results, query) {
10254
10370
  if (results.length === 0) {
10255
10371
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
10256
10372
  }
10257
10373
  const formatted = results.map((r, idx) => {
10258
- const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
10374
+ const header = formatResultHeader(r, idx);
10259
10375
  return `${header} (score: ${r.score.toFixed(2)})
10260
10376
  \`\`\`
10261
10377
  ${truncateContent(r.content)}
@@ -10265,7 +10381,7 @@ ${truncateContent(r.content)}
10265
10381
  }
10266
10382
  function formatSearchResults(results, scoreFormat = "similarity") {
10267
10383
  const formatted = results.map((r, idx) => {
10268
- const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
10384
+ const header = formatResultHeader(r, idx);
10269
10385
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
10270
10386
  return `${header} ${scoreLabel}
10271
10387
  \`\`\`
@@ -10275,9 +10391,99 @@ ${truncateContent(r.content)}
10275
10391
  return formatted.join("\n\n");
10276
10392
  }
10277
10393
 
10394
+ // src/tools/knowledge-base-paths.ts
10395
+ var path13 = __toESM(require("path"), 1);
10396
+ function resolveConfigPathValue(value, baseDir) {
10397
+ const trimmed = value.trim();
10398
+ if (!trimmed) {
10399
+ return trimmed;
10400
+ }
10401
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10402
+ return path13.normalize(absolutePath);
10403
+ }
10404
+ function serializeConfigPathValue(value, baseDir) {
10405
+ const trimmed = value.trim();
10406
+ if (!trimmed) {
10407
+ return trimmed;
10408
+ }
10409
+ if (!path13.isAbsolute(trimmed)) {
10410
+ return normalizePathSeparators(path13.normalize(trimmed));
10411
+ }
10412
+ const relativePath = path13.relative(baseDir, trimmed);
10413
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
10414
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
10415
+ }
10416
+ return path13.normalize(trimmed);
10417
+ }
10418
+ function resolveKnowledgeBasePath(value, projectRoot) {
10419
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
10420
+ }
10421
+ function normalizeKnowledgeBasePath2(value, projectRoot) {
10422
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
10423
+ }
10424
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
10425
+ const normalizedInput = path13.normalize(inputPath);
10426
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
10427
+ }
10428
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
10429
+ const normalizedInput = path13.normalize(inputPath);
10430
+ return knowledgeBases.findIndex(
10431
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
10432
+ );
10433
+ }
10434
+
10278
10435
  // src/tools/index.ts
10279
- var import_fs7 = require("fs");
10280
- var path9 = __toESM(require("path"), 1);
10436
+ var import_fs9 = require("fs");
10437
+ var path15 = __toESM(require("path"), 1);
10438
+
10439
+ // src/tools/config-state.ts
10440
+ var import_fs8 = require("fs");
10441
+ var path14 = __toESM(require("path"), 1);
10442
+ function normalizeKnowledgeBasePaths(config, projectRoot) {
10443
+ const normalized = { ...config };
10444
+ if (Array.isArray(normalized.knowledgeBases)) {
10445
+ normalized.knowledgeBases = normalized.knowledgeBases.map(
10446
+ (kb) => resolveConfigPathValue(kb, projectRoot)
10447
+ );
10448
+ }
10449
+ return normalized;
10450
+ }
10451
+ function toConfigRecord(rawConfig) {
10452
+ if (!rawConfig || typeof rawConfig !== "object") {
10453
+ return {};
10454
+ }
10455
+ return { ...rawConfig };
10456
+ }
10457
+ function getConfigPath(projectRoot) {
10458
+ return resolveWritableProjectConfigPath(projectRoot);
10459
+ }
10460
+ function loadRuntimeConfig(projectRoot) {
10461
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot)), projectRoot);
10462
+ }
10463
+ function loadEditableConfig(projectRoot) {
10464
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot)), projectRoot);
10465
+ }
10466
+ function saveConfig(projectRoot, config) {
10467
+ const configPath = getConfigPath(projectRoot);
10468
+ const configDir = path14.dirname(configPath);
10469
+ const configBaseDir = path14.dirname(configDir);
10470
+ if (!(0, import_fs8.existsSync)(configDir)) {
10471
+ (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10472
+ }
10473
+ const serializableConfig = { ...config };
10474
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
10475
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10476
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
10477
+ );
10478
+ }
10479
+ (0, import_fs8.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10480
+ }
10481
+
10482
+ // src/tools/index.ts
10483
+ var os5 = __toESM(require("os"), 1);
10484
+ function ensureStringArray(value) {
10485
+ return Array.isArray(value) ? value : [];
10486
+ }
10281
10487
  var z = import_plugin.tool.schema;
10282
10488
  var sharedIndexer = null;
10283
10489
  var sharedProjectRoot = "";
@@ -10292,20 +10498,20 @@ function refreshIndexerFromConfig() {
10292
10498
  if (!sharedProjectRoot) {
10293
10499
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10294
10500
  }
10295
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10501
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10296
10502
  }
10297
10503
  function shouldForceLocalizeProjectIndex() {
10298
- const currentConfig = parseConfig(loadRuntimeConfig());
10504
+ const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10299
10505
  if (currentConfig.scope !== "project") {
10300
10506
  return false;
10301
10507
  }
10302
- const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10508
+ const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10303
10509
  const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10304
10510
  if (!mainRepoRoot) {
10305
10511
  return false;
10306
10512
  }
10307
- const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10308
- return !(0, import_fs7.existsSync)(localIndexPath) && (0, import_fs7.existsSync)(inheritedIndexPath);
10513
+ const inheritedIndexPath = path15.join(mainRepoRoot, ".opencode", "index");
10514
+ return !(0, import_fs9.existsSync)(localIndexPath) && (0, import_fs9.existsSync)(inheritedIndexPath);
10309
10515
  }
10310
10516
  function getIndexer() {
10311
10517
  if (!sharedIndexer) {
@@ -10313,76 +10519,6 @@ function getIndexer() {
10313
10519
  }
10314
10520
  return sharedIndexer;
10315
10521
  }
10316
- function getConfigPath() {
10317
- return resolveWritableProjectConfigPath(sharedProjectRoot);
10318
- }
10319
- function normalizeConfigPathValue(value, baseDir) {
10320
- const trimmed = value.trim();
10321
- if (!trimmed) {
10322
- return trimmed;
10323
- }
10324
- const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10325
- return path9.normalize(absolutePath);
10326
- }
10327
- function serializeConfigPathValue(value, baseDir) {
10328
- const trimmed = value.trim();
10329
- if (!trimmed) {
10330
- return trimmed;
10331
- }
10332
- const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10333
- if (!path9.isAbsolute(trimmed)) {
10334
- return normalizeRelativePath(path9.normalize(trimmed));
10335
- }
10336
- const relativePath = path9.relative(baseDir, trimmed);
10337
- if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10338
- return normalizeRelativePath(path9.normalize(relativePath || "."));
10339
- }
10340
- return path9.normalize(trimmed);
10341
- }
10342
- function normalizeKnowledgeBasePaths(config) {
10343
- const normalized = { ...config };
10344
- if (Array.isArray(normalized.knowledgeBases)) {
10345
- normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10346
- return normalizeConfigPathValue(kb, sharedProjectRoot);
10347
- });
10348
- }
10349
- return normalized;
10350
- }
10351
- function loadRuntimeConfig() {
10352
- const rawConfig = loadMergedConfig(sharedProjectRoot);
10353
- const config = {};
10354
- if (rawConfig && typeof rawConfig === "object") {
10355
- for (const key of Object.keys(rawConfig)) {
10356
- config[key] = rawConfig[key];
10357
- }
10358
- }
10359
- return normalizeKnowledgeBasePaths(config);
10360
- }
10361
- function loadEditableConfig() {
10362
- const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10363
- const config = {};
10364
- if (rawConfig && typeof rawConfig === "object") {
10365
- for (const key of Object.keys(rawConfig)) {
10366
- config[key] = rawConfig[key];
10367
- }
10368
- }
10369
- return normalizeKnowledgeBasePaths(config);
10370
- }
10371
- function saveConfig(config) {
10372
- const configPath = getConfigPath();
10373
- const configDir = path9.dirname(configPath);
10374
- const configBaseDir = path9.dirname(configDir);
10375
- if (!(0, import_fs7.existsSync)(configDir)) {
10376
- (0, import_fs7.mkdirSync)(configDir, { recursive: true });
10377
- }
10378
- const serializableConfig = { ...config };
10379
- if (Array.isArray(serializableConfig.knowledgeBases)) {
10380
- serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10381
- (kb) => serializeConfigPathValue(kb, configBaseDir)
10382
- );
10383
- }
10384
- (0, import_fs7.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10385
- }
10386
10522
  var codebase_peek = (0, import_plugin.tool)({
10387
10523
  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.",
10388
10524
  args: {
@@ -10602,36 +10738,64 @@ var add_knowledge_base = (0, import_plugin.tool)({
10602
10738
  },
10603
10739
  async execute(args) {
10604
10740
  const inputPath = args.path.trim();
10605
- const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10606
- if (!(0, import_fs7.existsSync)(resolvedPath)) {
10607
- return `Error: Directory does not exist: ${resolvedPath}`;
10741
+ const normalizedPath = path15.resolve(
10742
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, sharedProjectRoot)
10743
+ );
10744
+ if (!(0, import_fs9.existsSync)(normalizedPath)) {
10745
+ return `Error: Directory does not exist: ${normalizedPath}`;
10746
+ }
10747
+ let realPath;
10748
+ try {
10749
+ realPath = (0, import_fs9.realpathSync)(normalizedPath);
10750
+ } catch {
10751
+ return `Error: Cannot resolve path: ${normalizedPath}`;
10752
+ }
10753
+ const blockedPrefixes = [
10754
+ "/etc",
10755
+ "/proc",
10756
+ "/sys",
10757
+ "/dev",
10758
+ "/boot",
10759
+ "/root",
10760
+ "/var/run",
10761
+ "/var/log"
10762
+ ];
10763
+ const homeDir = os5.homedir();
10764
+ const sensitiveDotDirs = [".ssh", ".gnupg", ".aws", ".config/gcloud", ".docker", ".kube"];
10765
+ for (const prefix of blockedPrefixes) {
10766
+ if (realPath === prefix || realPath.startsWith(prefix + "/")) {
10767
+ return `Error: Adding system directory as knowledge base is not allowed: ${normalizedPath}`;
10768
+ }
10769
+ }
10770
+ for (const dotDir of sensitiveDotDirs) {
10771
+ const sensitiveDir = path15.join(homeDir, dotDir);
10772
+ if (realPath === sensitiveDir || realPath.startsWith(sensitiveDir + "/")) {
10773
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
10774
+ }
10608
10775
  }
10609
10776
  try {
10610
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10777
+ const stat4 = (0, import_fs9.statSync)(normalizedPath);
10611
10778
  if (!stat4.isDirectory()) {
10612
- return `Error: Path is not a directory: ${resolvedPath}`;
10779
+ return `Error: Path is not a directory: ${normalizedPath}`;
10613
10780
  }
10614
10781
  } catch (error) {
10615
- return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
10782
+ return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
10616
10783
  }
10617
- const config = loadEditableConfig();
10618
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10619
- const normalizedPath = path9.normalize(resolvedPath);
10620
- const alreadyExists = knowledgeBases.some(
10621
- (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
10622
- );
10784
+ const config = loadEditableConfig(sharedProjectRoot);
10785
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10786
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10623
10787
  if (alreadyExists) {
10624
- return `Knowledge base already configured: ${resolvedPath}`;
10788
+ return `Knowledge base already configured: ${normalizedPath}`;
10625
10789
  }
10626
- knowledgeBases.push(resolvedPath);
10790
+ knowledgeBases.push(normalizedPath);
10627
10791
  config.knowledgeBases = knowledgeBases;
10628
- saveConfig(config);
10792
+ saveConfig(sharedProjectRoot, config);
10629
10793
  refreshIndexerFromConfig();
10630
- let result = `${resolvedPath}
10794
+ let result = `${normalizedPath}
10631
10795
  `;
10632
10796
  result += `Total knowledge bases: ${knowledgeBases.length}
10633
10797
  `;
10634
- result += `Config saved to: ${getConfigPath()}
10798
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10635
10799
  `;
10636
10800
  result += `
10637
10801
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10642,8 +10806,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10642
10806
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10643
10807
  args: {},
10644
10808
  async execute() {
10645
- const config = loadRuntimeConfig();
10646
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10809
+ const config = loadRuntimeConfig(sharedProjectRoot);
10810
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10647
10811
  if (knowledgeBases.length === 0) {
10648
10812
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
10649
10813
  }
@@ -10652,8 +10816,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10652
10816
  `;
10653
10817
  for (let i = 0; i < knowledgeBases.length; i++) {
10654
10818
  const kb = knowledgeBases[i];
10655
- const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10656
- const exists = (0, import_fs7.existsSync)(resolvedPath);
10819
+ const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10820
+ const exists = (0, import_fs9.existsSync)(resolvedPath);
10657
10821
  result += `[${i + 1}] ${kb}
10658
10822
  `;
10659
10823
  result += ` Resolved: ${resolvedPath}
@@ -10662,7 +10826,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10662
10826
  `;
10663
10827
  if (exists) {
10664
10828
  try {
10665
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10829
+ const stat4 = (0, import_fs9.statSync)(resolvedPath);
10666
10830
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
10667
10831
  `;
10668
10832
  } catch {
@@ -10670,7 +10834,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10670
10834
  }
10671
10835
  result += "\n";
10672
10836
  }
10673
- result += `Config file: ${getConfigPath()}`;
10837
+ result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10674
10838
  return result;
10675
10839
  }
10676
10840
  });
@@ -10681,15 +10845,12 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10681
10845
  },
10682
10846
  async execute(args) {
10683
10847
  const inputPath = args.path.trim();
10684
- const config = loadEditableConfig();
10685
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10848
+ const config = loadEditableConfig(sharedProjectRoot);
10849
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10686
10850
  if (knowledgeBases.length === 0) {
10687
10851
  return "No knowledge bases configured.";
10688
10852
  }
10689
- const normalizedInput = path9.normalize(inputPath);
10690
- const index = knowledgeBases.findIndex(
10691
- (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
10692
- );
10853
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10693
10854
  if (index === -1) {
10694
10855
  let result2 = `Knowledge base not found: ${inputPath}
10695
10856
 
@@ -10704,14 +10865,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10704
10865
  }
10705
10866
  const removed = knowledgeBases.splice(index, 1)[0];
10706
10867
  config.knowledgeBases = knowledgeBases;
10707
- saveConfig(config);
10868
+ saveConfig(sharedProjectRoot, config);
10708
10869
  refreshIndexerFromConfig();
10709
10870
  let result = `Removed: ${removed}
10710
10871
 
10711
10872
  `;
10712
10873
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10713
10874
  `;
10714
- result += `Config saved to: ${getConfigPath()}
10875
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10715
10876
  `;
10716
10877
  result += `
10717
10878
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -10720,8 +10881,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
10720
10881
  });
10721
10882
 
10722
10883
  // src/commands/loader.ts
10723
- var import_fs8 = require("fs");
10724
- var path10 = __toESM(require("path"), 1);
10884
+ var import_fs10 = require("fs");
10885
+ var path16 = __toESM(require("path"), 1);
10725
10886
  function parseFrontmatter(content) {
10726
10887
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10727
10888
  const match = content.match(frontmatterRegex);
@@ -10742,15 +10903,21 @@ function parseFrontmatter(content) {
10742
10903
  }
10743
10904
  function loadCommandsFromDirectory(commandsDir) {
10744
10905
  const commands = /* @__PURE__ */ new Map();
10745
- if (!(0, import_fs8.existsSync)(commandsDir)) {
10906
+ if (!(0, import_fs10.existsSync)(commandsDir)) {
10746
10907
  return commands;
10747
10908
  }
10748
- const files = (0, import_fs8.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10909
+ const files = (0, import_fs10.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10749
10910
  for (const file of files) {
10750
- const filePath = path10.join(commandsDir, file);
10751
- const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
10911
+ const filePath = path16.join(commandsDir, file);
10912
+ let content;
10913
+ try {
10914
+ content = (0, import_fs10.readFileSync)(filePath, "utf-8");
10915
+ } catch (error) {
10916
+ const message = error instanceof Error ? error.message : String(error);
10917
+ throw new Error(`Failed to load command file ${filePath}: ${message}`);
10918
+ }
10752
10919
  const { frontmatter, body } = parseFrontmatter(content);
10753
- const name = path10.basename(file, ".md");
10920
+ const name = path16.basename(file, ".md");
10754
10921
  const description = frontmatter.description || `Run the ${name} command`;
10755
10922
  commands.set(name, {
10756
10923
  description,
@@ -10760,7 +10927,7 @@ function loadCommandsFromDirectory(commandsDir) {
10760
10927
  return commands;
10761
10928
  }
10762
10929
 
10763
- // src/routing-hints.ts
10930
+ // src/routing-hints-patterns.ts
10764
10931
  var EXTERNAL_HINTS = [
10765
10932
  "docs",
10766
10933
  "documentation",
@@ -10851,6 +11018,8 @@ var SNAKE_PATTERN = /\b[a-z0-9]+_[a-z0-9_]+\b/g;
10851
11018
  var KEBAB_PATTERN = /\b[a-z0-9]+-[a-z0-9-]+\b/g;
10852
11019
  var BACKTICK_IDENTIFIER_PATTERN = /`([^`]+)`/g;
10853
11020
  var BACKTICK_IDENTIFIER_PRESENCE_PATTERN = /`([^`]+)`/;
11021
+ var DOUBLE_QUOTED_PATTERN = /"[^"]+"/;
11022
+ var SINGLE_QUOTED_PATTERN = /'[^']+'/;
10854
11023
  function normalizeText(text) {
10855
11024
  return text.trim().replace(/\s+/g, " ");
10856
11025
  }
@@ -10863,6 +11032,21 @@ function countWords(text) {
10863
11032
  }
10864
11033
  return text.split(/\s+/).filter(Boolean).length;
10865
11034
  }
11035
+ function isExternalLookup(text) {
11036
+ return URL_PATTERN.test(text) || includesHint(text, EXTERNAL_HINTS);
11037
+ }
11038
+ function hasConceptualDiscoveryHint(text) {
11039
+ return includesHint(text, CONCEPTUAL_DISCOVERY_HINTS);
11040
+ }
11041
+ function hasDefinitionHint(text) {
11042
+ return includesHint(text, DEFINITION_HINTS);
11043
+ }
11044
+ function hasExactMatchHint(text) {
11045
+ return includesHint(text, EXACT_MATCH_HINTS);
11046
+ }
11047
+ function hasNonDiscoveryHint(text) {
11048
+ return includesHint(text, NON_DISCOVERY_HINTS);
11049
+ }
10866
11050
  function hasIdentifierShape(text) {
10867
11051
  const matches = [
10868
11052
  ...text.match(CAMEL_OR_PASCAL_PATTERN) ?? [],
@@ -10878,11 +11062,13 @@ function hasIdentifierShape(text) {
10878
11062
  });
10879
11063
  }
10880
11064
  function containsQuotedIdentifier(text) {
10881
- return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || /"[^"]+"/.test(text) || /'[^']+'/.test(text);
11065
+ return BACKTICK_IDENTIFIER_PRESENCE_PATTERN.test(text) || DOUBLE_QUOTED_PATTERN.test(text) || SINGLE_QUOTED_PATTERN.test(text);
10882
11066
  }
10883
11067
  function looksLikeDirectPath(text) {
10884
11068
  return FILE_PATH_PATTERN.test(text) || /\b[a-z0-9_-]+\.(ts|tsx|js|jsx|rs|py|go|java|json|md|yaml|yml)\b/i.test(text);
10885
11069
  }
11070
+
11071
+ // src/routing-hints.ts
10886
11072
  function extractUserText(parts) {
10887
11073
  return normalizeText(
10888
11074
  parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join(" ")
@@ -10898,21 +11084,21 @@ function assessRoutingIntent(text) {
10898
11084
  reason: "empty_text"
10899
11085
  };
10900
11086
  }
10901
- if (URL_PATTERN.test(lowered) || includesHint(lowered, EXTERNAL_HINTS)) {
11087
+ if (isExternalLookup(lowered)) {
10902
11088
  return {
10903
11089
  intent: "external",
10904
11090
  text: normalizedText,
10905
11091
  reason: "external_lookup"
10906
11092
  };
10907
11093
  }
10908
- const hasConceptualHint = includesHint(lowered, CONCEPTUAL_DISCOVERY_HINTS);
10909
- const hasDefinitionHint = includesHint(lowered, DEFINITION_HINTS);
10910
- const hasExactMatchHint = includesHint(lowered, EXACT_MATCH_HINTS);
10911
- const hasNonDiscoveryHint = includesHint(lowered, NON_DISCOVERY_HINTS);
11094
+ const matchedConceptualHint = hasConceptualDiscoveryHint(lowered);
11095
+ const matchedDefinitionHint = hasDefinitionHint(lowered);
11096
+ const matchedExactMatchHint = hasExactMatchHint(lowered);
11097
+ const matchedNonDiscoveryHint = hasNonDiscoveryHint(lowered);
10912
11098
  const hasIdentifier = hasIdentifierShape(normalizedText);
10913
11099
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
10914
11100
  const shortQuery = countWords(lowered) <= 10;
10915
- if (hasNonDiscoveryHint && !hasConceptualHint) {
11101
+ if (matchedNonDiscoveryHint && !matchedConceptualHint) {
10916
11102
  return {
10917
11103
  intent: "other",
10918
11104
  text: normalizedText,
@@ -10926,21 +11112,21 @@ function assessRoutingIntent(text) {
10926
11112
  reason: "direct_path_reference"
10927
11113
  };
10928
11114
  }
10929
- if ((hasDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
11115
+ if ((matchedDefinitionHint || lowered.includes("where is") || lowered.includes("where are")) && (lowered.includes("defined") || lowered.includes("definition"))) {
10930
11116
  return {
10931
11117
  intent: "definition_lookup",
10932
11118
  text: normalizedText,
10933
11119
  reason: "definition_lookup_request"
10934
11120
  };
10935
11121
  }
10936
- if ((hasExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !hasConceptualHint && shortQuery) {
11122
+ if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
10937
11123
  return {
10938
11124
  intent: "exact_identifier",
10939
11125
  text: normalizedText,
10940
- reason: hasExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
11126
+ reason: matchedExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
10941
11127
  };
10942
11128
  }
10943
- if (hasConceptualHint) {
11129
+ if (matchedConceptualHint) {
10944
11130
  return {
10945
11131
  intent: "local_conceptual",
10946
11132
  text: normalizedText,
@@ -11038,9 +11224,9 @@ function replaceActiveWatcher(nextWatcher) {
11038
11224
  function getCommandsDir() {
11039
11225
  let currentDir = process.cwd();
11040
11226
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
11041
- currentDir = path11.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11227
+ currentDir = path17.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11042
11228
  }
11043
- return path11.join(currentDir, "..", "commands");
11229
+ return path17.join(currentDir, "..", "commands");
11044
11230
  }
11045
11231
  var plugin = async ({ directory }) => {
11046
11232
  try {