opencode-codebase-index 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
@@ -2111,13 +2179,13 @@ var NodeFsHandler = class {
2111
2179
  if (this.fsw.closed) {
2112
2180
  return;
2113
2181
  }
2114
- const dirname8 = sp.dirname(file);
2182
+ const dirname9 = sp.dirname(file);
2115
2183
  const basename5 = sp.basename(file);
2116
- const parent = this.fsw._getWatchedDir(dirname8);
2184
+ const parent = this.fsw._getWatchedDir(dirname9);
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,16 +2199,16 @@ 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
  }
2142
2210
  } catch (error) {
2143
- this.fsw._remove(dirname8, basename5);
2211
+ this.fsw._remove(dirname9, basename5);
2144
2212
  }
2145
2213
  } else if (parent.has(basename5)) {
2146
2214
  const at = newStats.atimeMs;
@@ -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,13 +7759,14 @@ var Indexer = class {
7651
7759
  }
7652
7760
  atomicWriteSync(targetPath, data) {
7653
7761
  const tempPath = `${targetPath}.tmp`;
7654
- (0, import_fs6.writeFileSync)(tempPath, data);
7655
- (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);
7656
7765
  }
7657
7766
  getScopedRoots() {
7658
- const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7767
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
7659
7768
  for (const kbRoot of this.config.knowledgeBases) {
7660
- roots.add(path8.resolve(this.projectRoot, kbRoot));
7769
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
7661
7770
  }
7662
7771
  return Array.from(roots);
7663
7772
  }
@@ -7666,22 +7775,22 @@ var Indexer = class {
7666
7775
  if (this.config.scope !== "global") {
7667
7776
  return branchName;
7668
7777
  }
7669
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7778
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7670
7779
  return `${projectHash}:${branchName}`;
7671
7780
  }
7672
7781
  getLegacyBranchCatalogKey() {
7673
7782
  return this.currentBranch || "default";
7674
7783
  }
7675
7784
  getLegacyMigrationMetadataKey() {
7676
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7785
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7677
7786
  return `index.globalBranchMigration.${projectHash}`;
7678
7787
  }
7679
7788
  getProjectEmbeddingStrategyMetadataKey() {
7680
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7789
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7681
7790
  return `index.embeddingStrategyVersion.${projectHash}`;
7682
7791
  }
7683
7792
  getProjectForceReembedMetadataKey() {
7684
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7793
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7685
7794
  return `index.forceReembed.${projectHash}`;
7686
7795
  }
7687
7796
  hasProjectForceReembedPending() {
@@ -7775,7 +7884,7 @@ var Indexer = class {
7775
7884
  if (!this.database) {
7776
7885
  return { chunkIds, symbolIds };
7777
7886
  }
7778
- const projectRootPath = path8.resolve(this.projectRoot);
7887
+ const projectRootPath = path12.resolve(this.projectRoot);
7779
7888
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
7780
7889
  ...Array.from(this.fileHashCache.keys()).filter(
7781
7890
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -7798,7 +7907,7 @@ var Indexer = class {
7798
7907
  if (this.config.scope !== "global") {
7799
7908
  return this.getBranchCatalogCleanupKeys();
7800
7909
  }
7801
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7910
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7802
7911
  const keys = /* @__PURE__ */ new Set();
7803
7912
  const projectChunkIdSet = new Set(projectChunkIds);
7804
7913
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7882,7 +7991,7 @@ var Indexer = class {
7882
7991
  if (!this.database || this.config.scope !== "global") {
7883
7992
  return false;
7884
7993
  }
7885
- const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7994
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7886
7995
  const roots = this.getScopedRoots();
7887
7996
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7888
7997
  return this.database.getAllBranches().some(
@@ -7916,7 +8025,7 @@ var Indexer = class {
7916
8025
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7917
8026
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7918
8027
  ]);
7919
- const projectRootPath = path8.resolve(this.projectRoot);
8028
+ const projectRootPath = path12.resolve(this.projectRoot);
7920
8029
  const projectLocalFilePaths = new Set(
7921
8030
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7922
8031
  );
@@ -7985,24 +8094,24 @@ var Indexer = class {
7985
8094
  };
7986
8095
  }
7987
8096
  checkForInterruptedIndexing() {
7988
- return (0, import_fs6.existsSync)(this.indexingLockPath);
8097
+ return (0, import_fs7.existsSync)(this.indexingLockPath);
7989
8098
  }
7990
8099
  acquireIndexingLock() {
7991
8100
  const lockData = {
7992
8101
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7993
8102
  pid: process.pid
7994
8103
  };
7995
- (0, import_fs6.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
8104
+ (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
7996
8105
  }
7997
8106
  releaseIndexingLock() {
7998
- if ((0, import_fs6.existsSync)(this.indexingLockPath)) {
7999
- (0, import_fs6.unlinkSync)(this.indexingLockPath);
8107
+ if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
8108
+ (0, import_fs7.unlinkSync)(this.indexingLockPath);
8000
8109
  }
8001
8110
  }
8002
8111
  async recoverFromInterruptedIndexing() {
8003
8112
  this.logger.warn("Detected interrupted indexing session, recovering...");
8004
- if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
8005
- (0, import_fs6.unlinkSync)(this.fileHashCachePath);
8113
+ if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
8114
+ (0, import_fs7.unlinkSync)(this.fileHashCachePath);
8006
8115
  }
8007
8116
  await this.healthCheck();
8008
8117
  this.releaseIndexingLock();
@@ -8011,15 +8120,20 @@ var Indexer = class {
8011
8120
  loadFailedBatches(maxChunkTokens) {
8012
8121
  try {
8013
8122
  return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8014
- } 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
+ });
8015
8129
  return [];
8016
8130
  }
8017
8131
  }
8018
8132
  loadSerializedFailedBatches() {
8019
- if (!(0, import_fs6.existsSync)(this.failedBatchesPath)) {
8133
+ if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
8020
8134
  return [];
8021
8135
  }
8022
- const data = (0, import_fs6.readFileSync)(this.failedBatchesPath, "utf-8");
8136
+ const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
8023
8137
  const parsed = JSON.parse(data);
8024
8138
  return parsed.map((batch) => {
8025
8139
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -8036,15 +8150,15 @@ var Indexer = class {
8036
8150
  }
8037
8151
  saveFailedBatches(batches) {
8038
8152
  if (batches.length === 0) {
8039
- if ((0, import_fs6.existsSync)(this.failedBatchesPath)) {
8153
+ if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
8040
8154
  try {
8041
- (0, import_fs6.unlinkSync)(this.failedBatchesPath);
8155
+ (0, import_fs7.unlinkSync)(this.failedBatchesPath);
8042
8156
  } catch {
8043
8157
  }
8044
8158
  }
8045
8159
  return;
8046
8160
  }
8047
- (0, import_fs6.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8161
+ (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8048
8162
  }
8049
8163
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8050
8164
  const retryableById = /* @__PURE__ */ new Map();
@@ -8224,7 +8338,7 @@ var Indexer = class {
8224
8338
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
8225
8339
  parts.push(`intent_hint: ${intent}`);
8226
8340
  try {
8227
- 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");
8228
8342
  const lines = fileContent.split("\n");
8229
8343
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
8230
8344
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -8269,26 +8383,26 @@ var Indexer = class {
8269
8383
  });
8270
8384
  }
8271
8385
  }
8272
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8386
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8273
8387
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
8274
- const storePath = path8.join(this.indexPath, "vectors");
8388
+ const storePath = path12.join(this.indexPath, "vectors");
8275
8389
  this.store = new VectorStore(storePath, dimensions);
8276
- const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8277
- if ((0, import_fs6.existsSync)(indexFilePath)) {
8390
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
8391
+ if ((0, import_fs7.existsSync)(indexFilePath)) {
8278
8392
  this.store.load();
8279
8393
  }
8280
- const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
8394
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
8281
8395
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8282
8396
  try {
8283
8397
  this.invertedIndex.load();
8284
8398
  } catch {
8285
- if ((0, import_fs6.existsSync)(invertedIndexPath)) {
8286
- await import_fs6.promises.unlink(invertedIndexPath);
8399
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
8400
+ await import_fs7.promises.unlink(invertedIndexPath);
8287
8401
  }
8288
8402
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8289
8403
  }
8290
- const dbPath = path8.join(this.indexPath, "codebase.db");
8291
- let dbIsNew = !(0, import_fs6.existsSync)(dbPath);
8404
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8405
+ let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
8292
8406
  try {
8293
8407
  this.database = new Database(dbPath);
8294
8408
  } catch (error) {
@@ -8368,7 +8482,7 @@ var Indexer = class {
8368
8482
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8369
8483
  return {
8370
8484
  resetCorruptedIndex: true,
8371
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8485
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8372
8486
  };
8373
8487
  }
8374
8488
  throw error;
@@ -8383,7 +8497,7 @@ var Indexer = class {
8383
8497
  return;
8384
8498
  }
8385
8499
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8386
- const storeBasePath = path8.join(this.indexPath, "vectors");
8500
+ const storeBasePath = path12.join(this.indexPath, "vectors");
8387
8501
  const storeIndexPath = `${storeBasePath}.usearch`;
8388
8502
  const storeMetadataPath = `${storeBasePath}.meta.json`;
8389
8503
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -8392,19 +8506,19 @@ var Indexer = class {
8392
8506
  let backedUpMetadata = false;
8393
8507
  let rebuiltCount = 0;
8394
8508
  let skippedCount = 0;
8395
- if ((0, import_fs6.existsSync)(backupIndexPath)) {
8396
- (0, import_fs6.unlinkSync)(backupIndexPath);
8509
+ if ((0, import_fs7.existsSync)(backupIndexPath)) {
8510
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8397
8511
  }
8398
- if ((0, import_fs6.existsSync)(backupMetadataPath)) {
8399
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8512
+ if ((0, import_fs7.existsSync)(backupMetadataPath)) {
8513
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8400
8514
  }
8401
8515
  try {
8402
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8403
- (0, import_fs6.renameSync)(storeIndexPath, backupIndexPath);
8516
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8517
+ (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
8404
8518
  backedUpIndex = true;
8405
8519
  }
8406
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8407
- (0, import_fs6.renameSync)(storeMetadataPath, backupMetadataPath);
8520
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8521
+ (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
8408
8522
  backedUpMetadata = true;
8409
8523
  }
8410
8524
  store.clear();
@@ -8424,11 +8538,11 @@ var Indexer = class {
8424
8538
  rebuiltCount += 1;
8425
8539
  }
8426
8540
  store.save();
8427
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8428
- (0, import_fs6.unlinkSync)(backupIndexPath);
8541
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8542
+ (0, import_fs7.unlinkSync)(backupIndexPath);
8429
8543
  }
8430
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8431
- (0, import_fs6.unlinkSync)(backupMetadataPath);
8544
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8545
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
8432
8546
  }
8433
8547
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8434
8548
  excludedChunks: excludedSet.size,
@@ -8440,17 +8554,17 @@ var Indexer = class {
8440
8554
  store.clear();
8441
8555
  } catch {
8442
8556
  }
8443
- if ((0, import_fs6.existsSync)(storeIndexPath)) {
8444
- (0, import_fs6.unlinkSync)(storeIndexPath);
8557
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
8558
+ (0, import_fs7.unlinkSync)(storeIndexPath);
8445
8559
  }
8446
- if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8447
- (0, import_fs6.unlinkSync)(storeMetadataPath);
8560
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
8561
+ (0, import_fs7.unlinkSync)(storeMetadataPath);
8448
8562
  }
8449
- if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8450
- (0, import_fs6.renameSync)(backupIndexPath, storeIndexPath);
8563
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
8564
+ (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
8451
8565
  }
8452
- if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8453
- (0, import_fs6.renameSync)(backupMetadataPath, storeMetadataPath);
8566
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
8567
+ (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
8454
8568
  }
8455
8569
  if (backedUpIndex || backedUpMetadata) {
8456
8570
  store.load();
@@ -8468,7 +8582,7 @@ var Indexer = class {
8468
8582
  if (!isSqliteCorruptionError(error)) {
8469
8583
  return false;
8470
8584
  }
8471
- const dbPath = path8.join(this.indexPath, "codebase.db");
8585
+ const dbPath = path12.join(this.indexPath, "codebase.db");
8472
8586
  const warning = this.getCorruptedIndexWarning(dbPath);
8473
8587
  const errorMessage = getErrorMessage(error);
8474
8588
  if (this.config.scope === "global") {
@@ -8491,23 +8605,23 @@ var Indexer = class {
8491
8605
  this.indexCompatibility = null;
8492
8606
  this.fileHashCache.clear();
8493
8607
  const resetPaths = [
8494
- path8.join(this.indexPath, "codebase.db"),
8495
- path8.join(this.indexPath, "codebase.db-shm"),
8496
- path8.join(this.indexPath, "codebase.db-wal"),
8497
- path8.join(this.indexPath, "vectors.usearch"),
8498
- path8.join(this.indexPath, "inverted-index.json"),
8499
- path8.join(this.indexPath, "file-hashes.json"),
8500
- path8.join(this.indexPath, "failed-batches.json"),
8501
- path8.join(this.indexPath, "indexing.lock"),
8502
- 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")
8503
8617
  ];
8504
8618
  await Promise.all(resetPaths.map(async (targetPath) => {
8505
8619
  try {
8506
- await import_fs6.promises.rm(targetPath, { recursive: true, force: true });
8620
+ await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
8507
8621
  } catch {
8508
8622
  }
8509
8623
  }));
8510
- await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8624
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8511
8625
  return true;
8512
8626
  }
8513
8627
  migrateFromLegacyIndex() {
@@ -8712,7 +8826,7 @@ var Indexer = class {
8712
8826
  unchangedFilePaths.add(f.path);
8713
8827
  this.logger.recordCacheHit();
8714
8828
  } else {
8715
- const content = await import_fs6.promises.readFile(f.path, "utf-8");
8829
+ const content = await import_fs7.promises.readFile(f.path, "utf-8");
8716
8830
  changedFiles.push({ path: f.path, content, hash: currentHash });
8717
8831
  this.logger.recordCacheMiss();
8718
8832
  }
@@ -8764,7 +8878,7 @@ var Indexer = class {
8764
8878
  for (const parsed of parsedFiles) {
8765
8879
  currentFilePaths.add(parsed.path);
8766
8880
  if (parsed.chunks.length === 0) {
8767
- const relativePath = path8.relative(this.projectRoot, parsed.path);
8881
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
8768
8882
  stats.parseFailures.push(relativePath);
8769
8883
  }
8770
8884
  let fileChunkCount = 0;
@@ -9047,7 +9161,7 @@ var Indexer = class {
9047
9161
  for (const requestBatch of requestBatches) {
9048
9162
  queue.add(async () => {
9049
9163
  if (rateLimitBackoffMs > 0) {
9050
- await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
9164
+ await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
9051
9165
  }
9052
9166
  try {
9053
9167
  const result = await pRetry(
@@ -9496,7 +9610,7 @@ var Indexer = class {
9496
9610
  let contextEndLine = r.metadata.endLine;
9497
9611
  if (!metadataOnly && this.config.search.includeContext) {
9498
9612
  try {
9499
- const fileContent = await import_fs6.promises.readFile(
9613
+ const fileContent = await import_fs7.promises.readFile(
9500
9614
  r.metadata.filePath,
9501
9615
  "utf-8"
9502
9616
  );
@@ -9608,8 +9722,8 @@ var Indexer = class {
9608
9722
  this.indexCompatibility = compatibility;
9609
9723
  return;
9610
9724
  }
9611
- const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9612
- 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)) {
9613
9727
  throw new Error(
9614
9728
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9615
9729
  );
@@ -9648,7 +9762,7 @@ var Indexer = class {
9648
9762
  const removedChunkKeys = [];
9649
9763
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9650
9764
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9651
- if (!(0, import_fs6.existsSync)(filePath)) {
9765
+ if (!(0, import_fs7.existsSync)(filePath)) {
9652
9766
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9653
9767
  for (const key of chunkKeys) {
9654
9768
  removedChunkKeys.push(key);
@@ -9697,7 +9811,7 @@ var Indexer = class {
9697
9811
  gcOrphanSymbols: 0,
9698
9812
  gcOrphanCallEdges: 0,
9699
9813
  resetCorruptedIndex: true,
9700
- warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9814
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9701
9815
  };
9702
9816
  }
9703
9817
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -10000,7 +10114,7 @@ var Indexer = class {
10000
10114
  let content = "";
10001
10115
  if (this.config.search.includeContext) {
10002
10116
  try {
10003
- const fileContent = await import_fs6.promises.readFile(
10117
+ const fileContent = await import_fs7.promises.readFile(
10004
10118
  r.metadata.filePath,
10005
10119
  "utf-8"
10006
10120
  );
@@ -10249,12 +10363,15 @@ function formatLogs(logs) {
10249
10363
  return `[${l.timestamp}] [${l.level.toUpperCase()}] [${l.category}] ${l.message}${dataStr}`;
10250
10364
  }).join("\n");
10251
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
+ }
10252
10369
  function formatDefinitionLookup(results, query) {
10253
10370
  if (results.length === 0) {
10254
10371
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
10255
10372
  }
10256
10373
  const formatted = results.map((r, idx) => {
10257
- const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
10374
+ const header = formatResultHeader(r, idx);
10258
10375
  return `${header} (score: ${r.score.toFixed(2)})
10259
10376
  \`\`\`
10260
10377
  ${truncateContent(r.content)}
@@ -10264,7 +10381,7 @@ ${truncateContent(r.content)}
10264
10381
  }
10265
10382
  function formatSearchResults(results, scoreFormat = "similarity") {
10266
10383
  const formatted = results.map((r, idx) => {
10267
- 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);
10268
10385
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
10269
10386
  return `${header} ${scoreLabel}
10270
10387
  \`\`\`
@@ -10274,9 +10391,99 @@ ${truncateContent(r.content)}
10274
10391
  return formatted.join("\n\n");
10275
10392
  }
10276
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
+
10277
10435
  // src/tools/index.ts
10278
- var import_fs7 = require("fs");
10279
- 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
+ }
10280
10487
  var z = import_plugin.tool.schema;
10281
10488
  var sharedIndexer = null;
10282
10489
  var sharedProjectRoot = "";
@@ -10291,20 +10498,20 @@ function refreshIndexerFromConfig() {
10291
10498
  if (!sharedProjectRoot) {
10292
10499
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
10293
10500
  }
10294
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10501
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig(sharedProjectRoot)));
10295
10502
  }
10296
10503
  function shouldForceLocalizeProjectIndex() {
10297
- const currentConfig = parseConfig(loadRuntimeConfig());
10504
+ const currentConfig = parseConfig(loadRuntimeConfig(sharedProjectRoot));
10298
10505
  if (currentConfig.scope !== "project") {
10299
10506
  return false;
10300
10507
  }
10301
- const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10508
+ const localIndexPath = path15.join(sharedProjectRoot, ".opencode", "index");
10302
10509
  const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10303
10510
  if (!mainRepoRoot) {
10304
10511
  return false;
10305
10512
  }
10306
- const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10307
- 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);
10308
10515
  }
10309
10516
  function getIndexer() {
10310
10517
  if (!sharedIndexer) {
@@ -10312,76 +10519,6 @@ function getIndexer() {
10312
10519
  }
10313
10520
  return sharedIndexer;
10314
10521
  }
10315
- function getConfigPath() {
10316
- return resolveWritableProjectConfigPath(sharedProjectRoot);
10317
- }
10318
- function normalizeConfigPathValue(value, baseDir) {
10319
- const trimmed = value.trim();
10320
- if (!trimmed) {
10321
- return trimmed;
10322
- }
10323
- const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10324
- return path9.normalize(absolutePath);
10325
- }
10326
- function serializeConfigPathValue(value, baseDir) {
10327
- const trimmed = value.trim();
10328
- if (!trimmed) {
10329
- return trimmed;
10330
- }
10331
- const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10332
- if (!path9.isAbsolute(trimmed)) {
10333
- return normalizeRelativePath(path9.normalize(trimmed));
10334
- }
10335
- const relativePath = path9.relative(baseDir, trimmed);
10336
- if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10337
- return normalizeRelativePath(path9.normalize(relativePath || "."));
10338
- }
10339
- return path9.normalize(trimmed);
10340
- }
10341
- function normalizeKnowledgeBasePaths(config) {
10342
- const normalized = { ...config };
10343
- if (Array.isArray(normalized.knowledgeBases)) {
10344
- normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10345
- return normalizeConfigPathValue(kb, sharedProjectRoot);
10346
- });
10347
- }
10348
- return normalized;
10349
- }
10350
- function loadRuntimeConfig() {
10351
- const rawConfig = loadMergedConfig(sharedProjectRoot);
10352
- const config = {};
10353
- if (rawConfig && typeof rawConfig === "object") {
10354
- for (const key of Object.keys(rawConfig)) {
10355
- config[key] = rawConfig[key];
10356
- }
10357
- }
10358
- return normalizeKnowledgeBasePaths(config);
10359
- }
10360
- function loadEditableConfig() {
10361
- const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10362
- const config = {};
10363
- if (rawConfig && typeof rawConfig === "object") {
10364
- for (const key of Object.keys(rawConfig)) {
10365
- config[key] = rawConfig[key];
10366
- }
10367
- }
10368
- return normalizeKnowledgeBasePaths(config);
10369
- }
10370
- function saveConfig(config) {
10371
- const configPath = getConfigPath();
10372
- const configDir = path9.dirname(configPath);
10373
- const configBaseDir = path9.dirname(configDir);
10374
- if (!(0, import_fs7.existsSync)(configDir)) {
10375
- (0, import_fs7.mkdirSync)(configDir, { recursive: true });
10376
- }
10377
- const serializableConfig = { ...config };
10378
- if (Array.isArray(serializableConfig.knowledgeBases)) {
10379
- serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10380
- (kb) => serializeConfigPathValue(kb, configBaseDir)
10381
- );
10382
- }
10383
- (0, import_fs7.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10384
- }
10385
10522
  var codebase_peek = (0, import_plugin.tool)({
10386
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.",
10387
10524
  args: {
@@ -10601,36 +10738,64 @@ var add_knowledge_base = (0, import_plugin.tool)({
10601
10738
  },
10602
10739
  async execute(args) {
10603
10740
  const inputPath = args.path.trim();
10604
- const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10605
- if (!(0, import_fs7.existsSync)(resolvedPath)) {
10606
- 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
+ }
10607
10775
  }
10608
10776
  try {
10609
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10777
+ const stat4 = (0, import_fs9.statSync)(normalizedPath);
10610
10778
  if (!stat4.isDirectory()) {
10611
- return `Error: Path is not a directory: ${resolvedPath}`;
10779
+ return `Error: Path is not a directory: ${normalizedPath}`;
10612
10780
  }
10613
10781
  } catch (error) {
10614
- 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)}`;
10615
10783
  }
10616
- const config = loadEditableConfig();
10617
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10618
- const normalizedPath = path9.normalize(resolvedPath);
10619
- const alreadyExists = knowledgeBases.some(
10620
- (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
10621
- );
10784
+ const config = loadEditableConfig(sharedProjectRoot);
10785
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10786
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, sharedProjectRoot);
10622
10787
  if (alreadyExists) {
10623
- return `Knowledge base already configured: ${resolvedPath}`;
10788
+ return `Knowledge base already configured: ${normalizedPath}`;
10624
10789
  }
10625
- knowledgeBases.push(resolvedPath);
10790
+ knowledgeBases.push(normalizedPath);
10626
10791
  config.knowledgeBases = knowledgeBases;
10627
- saveConfig(config);
10792
+ saveConfig(sharedProjectRoot, config);
10628
10793
  refreshIndexerFromConfig();
10629
- let result = `${resolvedPath}
10794
+ let result = `${normalizedPath}
10630
10795
  `;
10631
10796
  result += `Total knowledge bases: ${knowledgeBases.length}
10632
10797
  `;
10633
- result += `Config saved to: ${getConfigPath()}
10798
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10634
10799
  `;
10635
10800
  result += `
10636
10801
  Run /index to rebuild the index with the new knowledge base.`;
@@ -10641,8 +10806,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10641
10806
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
10642
10807
  args: {},
10643
10808
  async execute() {
10644
- const config = loadRuntimeConfig();
10645
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10809
+ const config = loadRuntimeConfig(sharedProjectRoot);
10810
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10646
10811
  if (knowledgeBases.length === 0) {
10647
10812
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
10648
10813
  }
@@ -10651,8 +10816,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10651
10816
  `;
10652
10817
  for (let i = 0; i < knowledgeBases.length; i++) {
10653
10818
  const kb = knowledgeBases[i];
10654
- const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10655
- const exists = (0, import_fs7.existsSync)(resolvedPath);
10819
+ const resolvedPath = resolveKnowledgeBasePath(kb, sharedProjectRoot);
10820
+ const exists = (0, import_fs9.existsSync)(resolvedPath);
10656
10821
  result += `[${i + 1}] ${kb}
10657
10822
  `;
10658
10823
  result += ` Resolved: ${resolvedPath}
@@ -10661,7 +10826,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10661
10826
  `;
10662
10827
  if (exists) {
10663
10828
  try {
10664
- const stat4 = (0, import_fs7.statSync)(resolvedPath);
10829
+ const stat4 = (0, import_fs9.statSync)(resolvedPath);
10665
10830
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
10666
10831
  `;
10667
10832
  } catch {
@@ -10669,7 +10834,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
10669
10834
  }
10670
10835
  result += "\n";
10671
10836
  }
10672
- result += `Config file: ${getConfigPath()}`;
10837
+ result += `Config file: ${getConfigPath(sharedProjectRoot)}`;
10673
10838
  return result;
10674
10839
  }
10675
10840
  });
@@ -10680,15 +10845,12 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10680
10845
  },
10681
10846
  async execute(args) {
10682
10847
  const inputPath = args.path.trim();
10683
- const config = loadEditableConfig();
10684
- const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
10848
+ const config = loadEditableConfig(sharedProjectRoot);
10849
+ const knowledgeBases = ensureStringArray(config.knowledgeBases);
10685
10850
  if (knowledgeBases.length === 0) {
10686
10851
  return "No knowledge bases configured.";
10687
10852
  }
10688
- const normalizedInput = path9.normalize(inputPath);
10689
- const index = knowledgeBases.findIndex(
10690
- (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
10691
- );
10853
+ const index = findKnowledgeBasePathIndex(knowledgeBases, inputPath, sharedProjectRoot);
10692
10854
  if (index === -1) {
10693
10855
  let result2 = `Knowledge base not found: ${inputPath}
10694
10856
 
@@ -10703,14 +10865,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
10703
10865
  }
10704
10866
  const removed = knowledgeBases.splice(index, 1)[0];
10705
10867
  config.knowledgeBases = knowledgeBases;
10706
- saveConfig(config);
10868
+ saveConfig(sharedProjectRoot, config);
10707
10869
  refreshIndexerFromConfig();
10708
10870
  let result = `Removed: ${removed}
10709
10871
 
10710
10872
  `;
10711
10873
  result += `Remaining knowledge bases: ${knowledgeBases.length}
10712
10874
  `;
10713
- result += `Config saved to: ${getConfigPath()}
10875
+ result += `Config saved to: ${getConfigPath(sharedProjectRoot)}
10714
10876
  `;
10715
10877
  result += `
10716
10878
  Run /index to rebuild the index without the removed knowledge base.`;
@@ -10719,8 +10881,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
10719
10881
  });
10720
10882
 
10721
10883
  // src/commands/loader.ts
10722
- var import_fs8 = require("fs");
10723
- var path10 = __toESM(require("path"), 1);
10884
+ var import_fs10 = require("fs");
10885
+ var path16 = __toESM(require("path"), 1);
10724
10886
  function parseFrontmatter(content) {
10725
10887
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
10726
10888
  const match = content.match(frontmatterRegex);
@@ -10741,15 +10903,21 @@ function parseFrontmatter(content) {
10741
10903
  }
10742
10904
  function loadCommandsFromDirectory(commandsDir) {
10743
10905
  const commands = /* @__PURE__ */ new Map();
10744
- if (!(0, import_fs8.existsSync)(commandsDir)) {
10906
+ if (!(0, import_fs10.existsSync)(commandsDir)) {
10745
10907
  return commands;
10746
10908
  }
10747
- 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"));
10748
10910
  for (const file of files) {
10749
- const filePath = path10.join(commandsDir, file);
10750
- 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
+ }
10751
10919
  const { frontmatter, body } = parseFrontmatter(content);
10752
- const name = path10.basename(file, ".md");
10920
+ const name = path16.basename(file, ".md");
10753
10921
  const description = frontmatter.description || `Run the ${name} command`;
10754
10922
  commands.set(name, {
10755
10923
  description,
@@ -10759,7 +10927,7 @@ function loadCommandsFromDirectory(commandsDir) {
10759
10927
  return commands;
10760
10928
  }
10761
10929
 
10762
- // src/routing-hints.ts
10930
+ // src/routing-hints-patterns.ts
10763
10931
  var EXTERNAL_HINTS = [
10764
10932
  "docs",
10765
10933
  "documentation",
@@ -10850,6 +11018,8 @@ var SNAKE_PATTERN = /\b[a-z0-9]+_[a-z0-9_]+\b/g;
10850
11018
  var KEBAB_PATTERN = /\b[a-z0-9]+-[a-z0-9-]+\b/g;
10851
11019
  var BACKTICK_IDENTIFIER_PATTERN = /`([^`]+)`/g;
10852
11020
  var BACKTICK_IDENTIFIER_PRESENCE_PATTERN = /`([^`]+)`/;
11021
+ var DOUBLE_QUOTED_PATTERN = /"[^"]+"/;
11022
+ var SINGLE_QUOTED_PATTERN = /'[^']+'/;
10853
11023
  function normalizeText(text) {
10854
11024
  return text.trim().replace(/\s+/g, " ");
10855
11025
  }
@@ -10862,6 +11032,21 @@ function countWords(text) {
10862
11032
  }
10863
11033
  return text.split(/\s+/).filter(Boolean).length;
10864
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
+ }
10865
11050
  function hasIdentifierShape(text) {
10866
11051
  const matches = [
10867
11052
  ...text.match(CAMEL_OR_PASCAL_PATTERN) ?? [],
@@ -10877,11 +11062,13 @@ function hasIdentifierShape(text) {
10877
11062
  });
10878
11063
  }
10879
11064
  function containsQuotedIdentifier(text) {
10880
- 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);
10881
11066
  }
10882
11067
  function looksLikeDirectPath(text) {
10883
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);
10884
11069
  }
11070
+
11071
+ // src/routing-hints.ts
10885
11072
  function extractUserText(parts) {
10886
11073
  return normalizeText(
10887
11074
  parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join(" ")
@@ -10897,21 +11084,21 @@ function assessRoutingIntent(text) {
10897
11084
  reason: "empty_text"
10898
11085
  };
10899
11086
  }
10900
- if (URL_PATTERN.test(lowered) || includesHint(lowered, EXTERNAL_HINTS)) {
11087
+ if (isExternalLookup(lowered)) {
10901
11088
  return {
10902
11089
  intent: "external",
10903
11090
  text: normalizedText,
10904
11091
  reason: "external_lookup"
10905
11092
  };
10906
11093
  }
10907
- const hasConceptualHint = includesHint(lowered, CONCEPTUAL_DISCOVERY_HINTS);
10908
- const hasDefinitionHint = includesHint(lowered, DEFINITION_HINTS);
10909
- const hasExactMatchHint = includesHint(lowered, EXACT_MATCH_HINTS);
10910
- 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);
10911
11098
  const hasIdentifier = hasIdentifierShape(normalizedText);
10912
11099
  const hasQuotedIdentifier = containsQuotedIdentifier(normalizedText);
10913
11100
  const shortQuery = countWords(lowered) <= 10;
10914
- if (hasNonDiscoveryHint && !hasConceptualHint) {
11101
+ if (matchedNonDiscoveryHint && !matchedConceptualHint) {
10915
11102
  return {
10916
11103
  intent: "other",
10917
11104
  text: normalizedText,
@@ -10925,21 +11112,21 @@ function assessRoutingIntent(text) {
10925
11112
  reason: "direct_path_reference"
10926
11113
  };
10927
11114
  }
10928
- 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"))) {
10929
11116
  return {
10930
11117
  intent: "definition_lookup",
10931
11118
  text: normalizedText,
10932
11119
  reason: "definition_lookup_request"
10933
11120
  };
10934
11121
  }
10935
- if ((hasExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !hasConceptualHint && shortQuery) {
11122
+ if ((matchedExactMatchHint || hasQuotedIdentifier || hasIdentifier) && !matchedConceptualHint && shortQuery) {
10936
11123
  return {
10937
11124
  intent: "exact_identifier",
10938
11125
  text: normalizedText,
10939
- reason: hasExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
11126
+ reason: matchedExactMatchHint || hasQuotedIdentifier ? "exact_match_request" : "identifier_shaped_query"
10940
11127
  };
10941
11128
  }
10942
- if (hasConceptualHint) {
11129
+ if (matchedConceptualHint) {
10943
11130
  return {
10944
11131
  intent: "local_conceptual",
10945
11132
  text: normalizedText,
@@ -11037,9 +11224,9 @@ function replaceActiveWatcher(nextWatcher) {
11037
11224
  function getCommandsDir() {
11038
11225
  let currentDir = process.cwd();
11039
11226
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
11040
- currentDir = path11.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11227
+ currentDir = path17.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11041
11228
  }
11042
- return path11.join(currentDir, "..", "commands");
11229
+ return path17.join(currentDir, "..", "commands");
11043
11230
  }
11044
11231
  var plugin = async ({ directory }) => {
11045
11232
  try {