opencode-codebase-index 0.7.0 → 0.8.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(path11, checkUnignored, mode) {
332
+ test(path12, 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(path11);
341
+ const matched = rule[mode].test(path12);
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 = (path11, originalPath, doThrow) => {
363
- if (!isString(path11)) {
362
+ var checkPath = (path12, originalPath, doThrow) => {
363
+ if (!isString(path12)) {
364
364
  return doThrow(
365
365
  `path must be a string, but got \`${originalPath}\``,
366
366
  TypeError
367
367
  );
368
368
  }
369
- if (!path11) {
369
+ if (!path12) {
370
370
  return doThrow(`path must not be empty`, TypeError);
371
371
  }
372
- if (checkPath.isNotRelative(path11)) {
372
+ if (checkPath.isNotRelative(path12)) {
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 = (path11) => REGEX_TEST_INVALID_PATH.test(path11);
381
+ var isNotRelative = (path12) => REGEX_TEST_INVALID_PATH.test(path12);
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 path11 = originalPath && checkPath.convert(originalPath);
411
+ const path12 = originalPath && checkPath.convert(originalPath);
412
412
  checkPath(
413
- path11,
413
+ path12,
414
414
  originalPath,
415
415
  this._strictPathCheck ? throwError : RETURN_FALSE
416
416
  );
417
- return this._t(path11, cache, checkUnignored, slices);
417
+ return this._t(path12, cache, checkUnignored, slices);
418
418
  }
419
- checkIgnore(path11) {
420
- if (!REGEX_TEST_TRAILING_SLASH.test(path11)) {
421
- return this.test(path11);
419
+ checkIgnore(path12) {
420
+ if (!REGEX_TEST_TRAILING_SLASH.test(path12)) {
421
+ return this.test(path12);
422
422
  }
423
- const slices = path11.split(SLASH2).filter(Boolean);
423
+ const slices = path12.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(path11, false, MODE_CHECK_IGNORE);
436
+ return this._rules.test(path12, false, MODE_CHECK_IGNORE);
437
437
  }
438
- _t(path11, cache, checkUnignored, slices) {
439
- if (path11 in cache) {
440
- return cache[path11];
438
+ _t(path12, cache, checkUnignored, slices) {
439
+ if (path12 in cache) {
440
+ return cache[path12];
441
441
  }
442
442
  if (!slices) {
443
- slices = path11.split(SLASH2).filter(Boolean);
443
+ slices = path12.split(SLASH2).filter(Boolean);
444
444
  }
445
445
  slices.pop();
446
446
  if (!slices.length) {
447
- return cache[path11] = this._rules.test(path11, checkUnignored, MODE_IGNORE);
447
+ return cache[path12] = this._rules.test(path12, 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[path11] = parent.ignored ? parent : this._rules.test(path11, checkUnignored, MODE_IGNORE);
455
+ return cache[path12] = parent.ignored ? parent : this._rules.test(path12, checkUnignored, MODE_IGNORE);
456
456
  }
457
- ignores(path11) {
458
- return this._test(path11, this._ignoreCache, false).ignored;
457
+ ignores(path12) {
458
+ return this._test(path12, this._ignoreCache, false).ignored;
459
459
  }
460
460
  createFilter() {
461
- return (path11) => !this.ignores(path11);
461
+ return (path12) => !this.ignores(path12);
462
462
  }
463
463
  filter(paths) {
464
464
  return makeArray(paths).filter(this.createFilter());
465
465
  }
466
466
  // @returns {TestResult}
467
- test(path11) {
468
- return this._test(path11, this._testCache, true);
467
+ test(path12) {
468
+ return this._test(path12, this._testCache, true);
469
469
  }
470
470
  };
471
471
  var factory = (options) => new Ignore2(options);
472
- var isPathValid = (path11) => checkPath(path11 && checkPath.convert(path11), path11, RETURN_FALSE);
472
+ var isPathValid = (path12) => checkPath(path12 && checkPath.convert(path12), path12, 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 = (path11) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path11) || isNotRelative(path11);
477
+ checkPath.isNotRelative = (path12) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path12) || isNotRelative(path12);
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 path10 = __toESM(require("path"), 1);
660
+ var path11 = __toESM(require("path"), 1);
661
661
  var import_url2 = require("url");
662
662
 
663
663
  // src/config/constants.ts
@@ -667,12 +667,14 @@ var DEFAULT_INCLUDE = [
667
667
  "**/*.{go,rs,java,kt,scala}",
668
668
  "**/*.{c,cpp,cc,h,hpp}",
669
669
  "**/*.{rb,php,inc,swift}",
670
+ "**/*.{cls,trigger}",
670
671
  "**/*.{vue,svelte,astro}",
671
672
  "**/*.{sql,graphql,proto}",
672
673
  "**/*.{yaml,yml,toml}",
673
674
  "**/*.{md,mdx}",
674
675
  "**/*.{sh,bash,zsh}",
675
- "**/*.{txt,html,htm}"
676
+ "**/*.{txt,html,htm}",
677
+ "**/*.zig"
676
678
  ];
677
679
  var DEFAULT_EXCLUDE = [
678
680
  "**/node_modules/**",
@@ -737,7 +739,7 @@ var EMBEDDING_MODELS = {
737
739
  provider: "ollama",
738
740
  model: "nomic-embed-text",
739
741
  dimensions: 768,
740
- maxTokens: 8192,
742
+ maxTokens: 2048,
741
743
  costPer1MTokens: 0
742
744
  },
743
745
  "mxbai-embed-large": {
@@ -761,9 +763,15 @@ var EMBEDDING_MODELS = {
761
763
  var DEFAULT_PROVIDER_MODELS = {
762
764
  "github-copilot": "text-embedding-3-small",
763
765
  "openai": "text-embedding-3-small",
764
- "google": "text-embedding-005",
766
+ "google": "gemini-embedding-001",
765
767
  "ollama": "nomic-embed-text"
766
768
  };
769
+ var AUTO_DETECT_PROVIDER_ORDER = [
770
+ "ollama",
771
+ "github-copilot",
772
+ "openai",
773
+ "google"
774
+ ];
767
775
 
768
776
  // src/config/env-substitution.ts
769
777
  var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
@@ -1022,26 +1030,261 @@ function getDefaultModelForProvider(provider) {
1022
1030
  return models[providerDefault];
1023
1031
  }
1024
1032
  var availableProviders = Object.keys(EMBEDDING_MODELS);
1033
+ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1034
+ (provider) => provider in EMBEDDING_MODELS
1035
+ );
1025
1036
 
1026
1037
  // src/config/merger.ts
1038
+ var import_fs3 = require("fs");
1039
+ var os2 = __toESM(require("os"), 1);
1040
+ var path3 = __toESM(require("path"), 1);
1041
+
1042
+ // src/config/paths.ts
1043
+ var import_fs2 = require("fs");
1044
+ var os = __toESM(require("os"), 1);
1045
+ var path2 = __toESM(require("path"), 1);
1046
+
1047
+ // src/git/index.ts
1027
1048
  var import_fs = require("fs");
1028
1049
  var path = __toESM(require("path"), 1);
1029
- var os = __toESM(require("os"), 1);
1050
+ function readPackedRefs(gitDir) {
1051
+ const packedRefsPath = path.join(gitDir, "packed-refs");
1052
+ if (!(0, import_fs.existsSync)(packedRefsPath)) {
1053
+ return [];
1054
+ }
1055
+ try {
1056
+ return (0, import_fs.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
1057
+ } catch {
1058
+ return [];
1059
+ }
1060
+ }
1061
+ function resolveCommonGitDir(gitDir) {
1062
+ const commonDirPath = path.join(gitDir, "commondir");
1063
+ if (!(0, import_fs.existsSync)(commonDirPath)) {
1064
+ return gitDir;
1065
+ }
1066
+ try {
1067
+ const raw = (0, import_fs.readFileSync)(commonDirPath, "utf-8").trim();
1068
+ if (!raw) {
1069
+ return gitDir;
1070
+ }
1071
+ const resolved = path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
1072
+ if ((0, import_fs.existsSync)(resolved)) {
1073
+ return resolved;
1074
+ }
1075
+ } catch {
1076
+ return gitDir;
1077
+ }
1078
+ return gitDir;
1079
+ }
1080
+ function resolveWorktreeMainRepoRoot(repoRoot) {
1081
+ const gitDir = resolveGitDir(repoRoot);
1082
+ if (!gitDir) {
1083
+ return null;
1084
+ }
1085
+ const commonGitDir = resolveCommonGitDir(gitDir);
1086
+ if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1087
+ return null;
1088
+ }
1089
+ const mainRepoRoot = path.dirname(commonGitDir);
1090
+ if (!(0, import_fs.existsSync)(mainRepoRoot)) {
1091
+ return null;
1092
+ }
1093
+ return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1094
+ }
1095
+ function resolveGitDir(repoRoot) {
1096
+ const gitPath = path.join(repoRoot, ".git");
1097
+ if (!(0, import_fs.existsSync)(gitPath)) {
1098
+ return null;
1099
+ }
1100
+ try {
1101
+ const stat4 = (0, import_fs.statSync)(gitPath);
1102
+ if (stat4.isDirectory()) {
1103
+ return gitPath;
1104
+ }
1105
+ if (stat4.isFile()) {
1106
+ const content = (0, import_fs.readFileSync)(gitPath, "utf-8").trim();
1107
+ const match = content.match(/^gitdir:\s*(.+)$/);
1108
+ if (match) {
1109
+ const gitdir = match[1];
1110
+ const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1111
+ if ((0, import_fs.existsSync)(resolvedPath)) {
1112
+ return resolvedPath;
1113
+ }
1114
+ }
1115
+ }
1116
+ } catch {
1117
+ }
1118
+ return null;
1119
+ }
1120
+ function isGitRepo(dir) {
1121
+ return resolveGitDir(dir) !== null;
1122
+ }
1123
+ function getCurrentBranch(repoRoot) {
1124
+ const gitDir = resolveGitDir(repoRoot);
1125
+ if (!gitDir) {
1126
+ return null;
1127
+ }
1128
+ const headPath = path.join(gitDir, "HEAD");
1129
+ if (!(0, import_fs.existsSync)(headPath)) {
1130
+ return null;
1131
+ }
1132
+ try {
1133
+ const headContent = (0, import_fs.readFileSync)(headPath, "utf-8").trim();
1134
+ const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1135
+ if (match) {
1136
+ return match[1];
1137
+ }
1138
+ if (/^[0-9a-f]{40}$/i.test(headContent)) {
1139
+ return headContent.slice(0, 7);
1140
+ }
1141
+ return null;
1142
+ } catch {
1143
+ return null;
1144
+ }
1145
+ }
1146
+ function getBaseBranch(repoRoot) {
1147
+ const gitDir = resolveGitDir(repoRoot);
1148
+ const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
1149
+ const candidates = ["main", "master", "develop", "trunk"];
1150
+ if (refStoreDir) {
1151
+ for (const candidate of candidates) {
1152
+ const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1153
+ if ((0, import_fs.existsSync)(refPath)) {
1154
+ return candidate;
1155
+ }
1156
+ const packedRefs = readPackedRefs(refStoreDir);
1157
+ if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
1158
+ return candidate;
1159
+ }
1160
+ }
1161
+ }
1162
+ return getCurrentBranch(repoRoot) ?? "main";
1163
+ }
1164
+ function getBranchOrDefault(repoRoot) {
1165
+ if (!isGitRepo(repoRoot)) {
1166
+ return "default";
1167
+ }
1168
+ return getCurrentBranch(repoRoot) ?? "default";
1169
+ }
1170
+ function getHeadPath(repoRoot) {
1171
+ const gitDir = resolveGitDir(repoRoot);
1172
+ if (gitDir) {
1173
+ return path.join(gitDir, "HEAD");
1174
+ }
1175
+ return path.join(repoRoot, ".git", "HEAD");
1176
+ }
1177
+
1178
+ // 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");
1181
+ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1182
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1183
+ if (!mainRepoRoot) {
1184
+ return null;
1185
+ }
1186
+ const fallbackPath = path2.join(mainRepoRoot, relativePath);
1187
+ return (0, import_fs2.existsSync)(fallbackPath) ? fallbackPath : null;
1188
+ }
1189
+ function hasProjectConfig(projectRoot) {
1190
+ return (0, import_fs2.existsSync)(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1191
+ }
1192
+ function getGlobalIndexPath() {
1193
+ return path2.join(os.homedir(), ".opencode", "global-index");
1194
+ }
1195
+ function resolveProjectConfigPath(projectRoot) {
1196
+ const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1197
+ if ((0, import_fs2.existsSync)(localConfigPath)) {
1198
+ return localConfigPath;
1199
+ }
1200
+ return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1201
+ }
1202
+ function resolveWritableProjectConfigPath(projectRoot) {
1203
+ return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1204
+ }
1205
+ function resolveProjectIndexPath(projectRoot, scope) {
1206
+ if (scope === "global") {
1207
+ return getGlobalIndexPath();
1208
+ }
1209
+ const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1210
+ if ((0, import_fs2.existsSync)(localIndexPath)) {
1211
+ return localIndexPath;
1212
+ }
1213
+ if (hasProjectConfig(projectRoot)) {
1214
+ return localIndexPath;
1215
+ }
1216
+ return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1217
+ }
1218
+
1219
+ // src/config/merger.ts
1030
1220
  function loadJsonFile(filePath) {
1031
1221
  try {
1032
- if ((0, import_fs.existsSync)(filePath)) {
1033
- const content = (0, import_fs.readFileSync)(filePath, "utf-8");
1222
+ if ((0, import_fs3.existsSync)(filePath)) {
1223
+ const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
1034
1224
  return JSON.parse(content);
1035
1225
  }
1036
1226
  } catch {
1037
1227
  }
1038
1228
  return null;
1039
1229
  }
1230
+ function normalizeRelativeConfigPath(candidate) {
1231
+ return candidate.replace(/\\/g, "/");
1232
+ }
1233
+ function isWithinRoot(rootDir, targetPath) {
1234
+ const relativePath = path3.relative(rootDir, targetPath);
1235
+ return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1236
+ }
1237
+ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1238
+ if (!Array.isArray(values)) {
1239
+ return [];
1240
+ }
1241
+ return values.filter((value) => typeof value === "string").map((value) => {
1242
+ const trimmed = value.trim();
1243
+ if (!trimmed) {
1244
+ return trimmed;
1245
+ }
1246
+ if (path3.isAbsolute(trimmed)) {
1247
+ if (isWithinRoot(sourceRoot, trimmed)) {
1248
+ return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1249
+ }
1250
+ return path3.normalize(trimmed);
1251
+ }
1252
+ const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1253
+ if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1254
+ return normalizeRelativeConfigPath(path3.normalize(trimmed));
1255
+ }
1256
+ return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1257
+ }).filter(Boolean);
1258
+ }
1259
+ 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");
1263
+ return localConfigPath;
1264
+ }
1265
+ function loadProjectConfigLayer(projectRoot) {
1266
+ const projectConfigPath = resolveProjectConfigPath(projectRoot);
1267
+ const projectConfig = loadJsonFile(projectConfigPath);
1268
+ if (!projectConfig) {
1269
+ return {};
1270
+ }
1271
+ const normalizedConfig = { ...projectConfig };
1272
+ const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1273
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
1274
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1275
+ normalizedConfig.knowledgeBases,
1276
+ projectConfigBaseDir,
1277
+ projectRoot
1278
+ );
1279
+ }
1280
+ return normalizedConfig;
1281
+ }
1040
1282
  function loadMergedConfig(projectRoot) {
1041
- const globalConfigPath = path.join(os.homedir(), ".config", "opencode", "codebase-index.json");
1283
+ const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1042
1284
  const globalConfig = loadJsonFile(globalConfigPath);
1043
- const projectConfigPath = path.join(projectRoot, ".opencode", "codebase-index.json");
1285
+ const projectConfigPath = resolveProjectConfigPath(projectRoot);
1044
1286
  const projectConfig = loadJsonFile(projectConfigPath);
1287
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1045
1288
  if (!globalConfig && !projectConfig) {
1046
1289
  return {};
1047
1290
  }
@@ -1049,56 +1292,56 @@ function loadMergedConfig(projectRoot) {
1049
1292
  return globalConfig;
1050
1293
  }
1051
1294
  if (!globalConfig && projectConfig) {
1052
- return projectConfig;
1295
+ return normalizedProjectConfig;
1053
1296
  }
1054
1297
  const merged = { ...globalConfig };
1055
- if (projectConfig && "embeddingProvider" in projectConfig) {
1056
- merged.embeddingProvider = projectConfig.embeddingProvider;
1298
+ if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
1299
+ merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
1057
1300
  } else if (globalConfig && globalConfig.embeddingProvider) {
1058
1301
  merged.embeddingProvider = globalConfig.embeddingProvider;
1059
1302
  }
1060
- if (projectConfig && "customProvider" in projectConfig) {
1061
- merged.customProvider = projectConfig.customProvider;
1303
+ if (projectConfig && "customProvider" in normalizedProjectConfig) {
1304
+ merged.customProvider = normalizedProjectConfig.customProvider;
1062
1305
  } else if (globalConfig && globalConfig.customProvider) {
1063
1306
  merged.customProvider = globalConfig.customProvider;
1064
1307
  }
1065
- if (projectConfig && "embeddingModel" in projectConfig) {
1066
- merged.embeddingModel = projectConfig.embeddingModel;
1308
+ if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
1309
+ merged.embeddingModel = normalizedProjectConfig.embeddingModel;
1067
1310
  } else if (globalConfig && globalConfig.embeddingModel) {
1068
1311
  merged.embeddingModel = globalConfig.embeddingModel;
1069
1312
  }
1070
- if (projectConfig && "reranker" in projectConfig) {
1071
- merged.reranker = projectConfig.reranker;
1313
+ if (projectConfig && "reranker" in normalizedProjectConfig) {
1314
+ merged.reranker = normalizedProjectConfig.reranker;
1072
1315
  } else if (globalConfig && globalConfig.reranker) {
1073
1316
  merged.reranker = globalConfig.reranker;
1074
1317
  }
1075
- if (projectConfig && "include" in projectConfig) {
1076
- merged.include = projectConfig.include;
1318
+ if (projectConfig && "include" in normalizedProjectConfig) {
1319
+ merged.include = normalizedProjectConfig.include;
1077
1320
  } else if (globalConfig && globalConfig.include) {
1078
1321
  merged.include = globalConfig.include;
1079
1322
  }
1080
- if (projectConfig && "exclude" in projectConfig) {
1081
- merged.exclude = projectConfig.exclude;
1323
+ if (projectConfig && "exclude" in normalizedProjectConfig) {
1324
+ merged.exclude = normalizedProjectConfig.exclude;
1082
1325
  } else if (globalConfig && globalConfig.exclude) {
1083
1326
  merged.exclude = globalConfig.exclude;
1084
1327
  }
1085
- if (projectConfig && "indexing" in projectConfig) {
1086
- merged.indexing = projectConfig.indexing;
1328
+ if (projectConfig && "indexing" in normalizedProjectConfig) {
1329
+ merged.indexing = normalizedProjectConfig.indexing;
1087
1330
  } else if (globalConfig && globalConfig.indexing) {
1088
1331
  merged.indexing = globalConfig.indexing;
1089
1332
  }
1090
- if (projectConfig && "search" in projectConfig) {
1091
- merged.search = projectConfig.search;
1333
+ if (projectConfig && "search" in normalizedProjectConfig) {
1334
+ merged.search = normalizedProjectConfig.search;
1092
1335
  } else if (globalConfig && globalConfig.search) {
1093
1336
  merged.search = globalConfig.search;
1094
1337
  }
1095
- if (projectConfig && "debug" in projectConfig) {
1096
- merged.debug = projectConfig.debug;
1338
+ if (projectConfig && "debug" in normalizedProjectConfig) {
1339
+ merged.debug = normalizedProjectConfig.debug;
1097
1340
  } else if (globalConfig && globalConfig.debug) {
1098
1341
  merged.debug = globalConfig.debug;
1099
1342
  }
1100
- if (projectConfig && "scope" in projectConfig) {
1101
- merged.scope = projectConfig.scope;
1343
+ if (projectConfig && "scope" in normalizedProjectConfig) {
1344
+ merged.scope = normalizedProjectConfig.scope;
1102
1345
  } else if (globalConfig && "scope" in globalConfig) {
1103
1346
  merged.scope = globalConfig.scope;
1104
1347
  }
@@ -1107,11 +1350,11 @@ function loadMergedConfig(projectRoot) {
1107
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") {
1108
1351
  continue;
1109
1352
  }
1110
- merged[key] = projectConfig[key];
1353
+ merged[key] = normalizedProjectConfig[key];
1111
1354
  }
1112
1355
  }
1113
1356
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1114
- const projectKbs = projectConfig && Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
1357
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1115
1358
  const allKbs = [...globalKbs, ...projectKbs];
1116
1359
  const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1117
1360
  merged.knowledgeBases = uniqueKbs;
@@ -1213,7 +1456,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1213
1456
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1214
1457
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
1215
1458
  if (wantBigintFsStats) {
1216
- this._stat = (path11) => statMethod(path11, { bigint: true });
1459
+ this._stat = (path12) => statMethod(path12, { bigint: true });
1217
1460
  } else {
1218
1461
  this._stat = statMethod;
1219
1462
  }
@@ -1238,8 +1481,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1238
1481
  const par = this.parent;
1239
1482
  const fil = par && par.files;
1240
1483
  if (fil && fil.length > 0) {
1241
- const { path: path11, depth } = par;
1242
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path11));
1484
+ const { path: path12, depth } = par;
1485
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1243
1486
  const awaited = await Promise.all(slice);
1244
1487
  for (const entry of awaited) {
1245
1488
  if (!entry)
@@ -1279,21 +1522,21 @@ var ReaddirpStream = class extends import_node_stream.Readable {
1279
1522
  this.reading = false;
1280
1523
  }
1281
1524
  }
1282
- async _exploreDir(path11, depth) {
1525
+ async _exploreDir(path12, depth) {
1283
1526
  let files;
1284
1527
  try {
1285
- files = await (0, import_promises.readdir)(path11, this._rdOptions);
1528
+ files = await (0, import_promises.readdir)(path12, this._rdOptions);
1286
1529
  } catch (error) {
1287
1530
  this._onError(error);
1288
1531
  }
1289
- return { files, depth, path: path11 };
1532
+ return { files, depth, path: path12 };
1290
1533
  }
1291
- async _formatEntry(dirent, path11) {
1534
+ async _formatEntry(dirent, path12) {
1292
1535
  let entry;
1293
- const basename4 = this._isDirent ? dirent.name : dirent;
1536
+ const basename5 = this._isDirent ? dirent.name : dirent;
1294
1537
  try {
1295
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path11, basename4));
1296
- entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename4 };
1538
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path12, basename5));
1539
+ entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
1297
1540
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1298
1541
  } catch (err) {
1299
1542
  this._onError(err);
@@ -1692,16 +1935,16 @@ var delFromSet = (main, prop, item) => {
1692
1935
  };
1693
1936
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1694
1937
  var FsWatchInstances = /* @__PURE__ */ new Map();
1695
- function createFsWatchInstance(path11, options, listener, errHandler, emitRaw) {
1938
+ function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
1696
1939
  const handleEvent = (rawEvent, evPath) => {
1697
- listener(path11);
1698
- emitRaw(rawEvent, evPath, { watchedPath: path11 });
1699
- if (evPath && path11 !== evPath) {
1700
- fsWatchBroadcast(sp.resolve(path11, evPath), KEY_LISTENERS, sp.join(path11, 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));
1701
1944
  }
1702
1945
  };
1703
1946
  try {
1704
- return (0, import_node_fs.watch)(path11, {
1947
+ return (0, import_node_fs.watch)(path12, {
1705
1948
  persistent: options.persistent
1706
1949
  }, handleEvent);
1707
1950
  } catch (error) {
@@ -1717,12 +1960,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1717
1960
  listener(val1, val2, val3);
1718
1961
  });
1719
1962
  };
1720
- var setFsWatchListener = (path11, fullPath, options, handlers) => {
1963
+ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1721
1964
  const { listener, errHandler, rawEmitter } = handlers;
1722
1965
  let cont = FsWatchInstances.get(fullPath);
1723
1966
  let watcher;
1724
1967
  if (!options.persistent) {
1725
- watcher = createFsWatchInstance(path11, options, listener, errHandler, rawEmitter);
1968
+ watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
1726
1969
  if (!watcher)
1727
1970
  return;
1728
1971
  return watcher.close.bind(watcher);
@@ -1733,7 +1976,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1733
1976
  addAndConvert(cont, KEY_RAW, rawEmitter);
1734
1977
  } else {
1735
1978
  watcher = createFsWatchInstance(
1736
- path11,
1979
+ path12,
1737
1980
  options,
1738
1981
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1739
1982
  errHandler,
@@ -1748,7 +1991,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1748
1991
  cont.watcherUnusable = true;
1749
1992
  if (isWindows && error.code === "EPERM") {
1750
1993
  try {
1751
- const fd = await (0, import_promises2.open)(path11, "r");
1994
+ const fd = await (0, import_promises2.open)(path12, "r");
1752
1995
  await fd.close();
1753
1996
  broadcastErr(error);
1754
1997
  } catch (err) {
@@ -1779,7 +2022,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1779
2022
  };
1780
2023
  };
1781
2024
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
1782
- var setFsWatchFileListener = (path11, fullPath, options, handlers) => {
2025
+ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
1783
2026
  const { listener, rawEmitter } = handlers;
1784
2027
  let cont = FsWatchFileInstances.get(fullPath);
1785
2028
  const copts = cont && cont.options;
@@ -1801,7 +2044,7 @@ var setFsWatchFileListener = (path11, fullPath, options, handlers) => {
1801
2044
  });
1802
2045
  const currmtime = curr.mtimeMs;
1803
2046
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
1804
- foreach(cont.listeners, (listener2) => listener2(path11, curr));
2047
+ foreach(cont.listeners, (listener2) => listener2(path12, curr));
1805
2048
  }
1806
2049
  })
1807
2050
  };
@@ -1831,13 +2074,13 @@ var NodeFsHandler = class {
1831
2074
  * @param listener on fs change
1832
2075
  * @returns closer for the watcher instance
1833
2076
  */
1834
- _watchWithNodeFs(path11, listener) {
2077
+ _watchWithNodeFs(path12, listener) {
1835
2078
  const opts = this.fsw.options;
1836
- const directory = sp.dirname(path11);
1837
- const basename4 = sp.basename(path11);
2079
+ const directory = sp.dirname(path12);
2080
+ const basename5 = sp.basename(path12);
1838
2081
  const parent = this.fsw._getWatchedDir(directory);
1839
- parent.add(basename4);
1840
- const absolutePath = sp.resolve(path11);
2082
+ parent.add(basename5);
2083
+ const absolutePath = sp.resolve(path12);
1841
2084
  const options = {
1842
2085
  persistent: opts.persistent
1843
2086
  };
@@ -1846,13 +2089,13 @@ var NodeFsHandler = class {
1846
2089
  let closer;
1847
2090
  if (opts.usePolling) {
1848
2091
  const enableBin = opts.interval !== opts.binaryInterval;
1849
- options.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
1850
- closer = setFsWatchFileListener(path11, absolutePath, options, {
2092
+ options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2093
+ closer = setFsWatchFileListener(path12, absolutePath, options, {
1851
2094
  listener,
1852
2095
  rawEmitter: this.fsw._emitRaw
1853
2096
  });
1854
2097
  } else {
1855
- closer = setFsWatchListener(path11, absolutePath, options, {
2098
+ closer = setFsWatchListener(path12, absolutePath, options, {
1856
2099
  listener,
1857
2100
  errHandler: this._boundHandleError,
1858
2101
  rawEmitter: this.fsw._emitRaw
@@ -1868,13 +2111,13 @@ var NodeFsHandler = class {
1868
2111
  if (this.fsw.closed) {
1869
2112
  return;
1870
2113
  }
1871
- const dirname5 = sp.dirname(file);
1872
- const basename4 = sp.basename(file);
1873
- const parent = this.fsw._getWatchedDir(dirname5);
2114
+ const dirname8 = sp.dirname(file);
2115
+ const basename5 = sp.basename(file);
2116
+ const parent = this.fsw._getWatchedDir(dirname8);
1874
2117
  let prevStats = stats;
1875
- if (parent.has(basename4))
2118
+ if (parent.has(basename5))
1876
2119
  return;
1877
- const listener = async (path11, newStats) => {
2120
+ const listener = async (path12, newStats) => {
1878
2121
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
1879
2122
  return;
1880
2123
  if (!newStats || newStats.mtimeMs === 0) {
@@ -1888,18 +2131,18 @@ var NodeFsHandler = class {
1888
2131
  this.fsw._emit(EV.CHANGE, file, newStats2);
1889
2132
  }
1890
2133
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
1891
- this.fsw._closeFile(path11);
2134
+ this.fsw._closeFile(path12);
1892
2135
  prevStats = newStats2;
1893
2136
  const closer2 = this._watchWithNodeFs(file, listener);
1894
2137
  if (closer2)
1895
- this.fsw._addPathCloser(path11, closer2);
2138
+ this.fsw._addPathCloser(path12, closer2);
1896
2139
  } else {
1897
2140
  prevStats = newStats2;
1898
2141
  }
1899
2142
  } catch (error) {
1900
- this.fsw._remove(dirname5, basename4);
2143
+ this.fsw._remove(dirname8, basename5);
1901
2144
  }
1902
- } else if (parent.has(basename4)) {
2145
+ } else if (parent.has(basename5)) {
1903
2146
  const at = newStats.atimeMs;
1904
2147
  const mt = newStats.mtimeMs;
1905
2148
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -1924,7 +2167,7 @@ var NodeFsHandler = class {
1924
2167
  * @param item basename of this item
1925
2168
  * @returns true if no more processing is needed for this entry.
1926
2169
  */
1927
- async _handleSymlink(entry, directory, path11, item) {
2170
+ async _handleSymlink(entry, directory, path12, item) {
1928
2171
  if (this.fsw.closed) {
1929
2172
  return;
1930
2173
  }
@@ -1934,7 +2177,7 @@ var NodeFsHandler = class {
1934
2177
  this.fsw._incrReadyCount();
1935
2178
  let linkPath;
1936
2179
  try {
1937
- linkPath = await (0, import_promises2.realpath)(path11);
2180
+ linkPath = await (0, import_promises2.realpath)(path12);
1938
2181
  } catch (e) {
1939
2182
  this.fsw._emitReady();
1940
2183
  return true;
@@ -1944,12 +2187,12 @@ var NodeFsHandler = class {
1944
2187
  if (dir.has(item)) {
1945
2188
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
1946
2189
  this.fsw._symlinkPaths.set(full, linkPath);
1947
- this.fsw._emit(EV.CHANGE, path11, entry.stats);
2190
+ this.fsw._emit(EV.CHANGE, path12, entry.stats);
1948
2191
  }
1949
2192
  } else {
1950
2193
  dir.add(item);
1951
2194
  this.fsw._symlinkPaths.set(full, linkPath);
1952
- this.fsw._emit(EV.ADD, path11, entry.stats);
2195
+ this.fsw._emit(EV.ADD, path12, entry.stats);
1953
2196
  }
1954
2197
  this.fsw._emitReady();
1955
2198
  return true;
@@ -1979,9 +2222,9 @@ var NodeFsHandler = class {
1979
2222
  return;
1980
2223
  }
1981
2224
  const item = entry.path;
1982
- let path11 = sp.join(directory, item);
2225
+ let path12 = sp.join(directory, item);
1983
2226
  current.add(item);
1984
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path11, item)) {
2227
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
1985
2228
  return;
1986
2229
  }
1987
2230
  if (this.fsw.closed) {
@@ -1990,11 +2233,11 @@ var NodeFsHandler = class {
1990
2233
  }
1991
2234
  if (item === target || !target && !previous.has(item)) {
1992
2235
  this.fsw._incrReadyCount();
1993
- path11 = sp.join(dir, sp.relative(dir, path11));
1994
- this._addToNodeFs(path11, initialAdd, wh, depth + 1);
2236
+ path12 = sp.join(dir, sp.relative(dir, path12));
2237
+ this._addToNodeFs(path12, initialAdd, wh, depth + 1);
1995
2238
  }
1996
2239
  }).on(EV.ERROR, this._boundHandleError);
1997
- return new Promise((resolve7, reject) => {
2240
+ return new Promise((resolve9, reject) => {
1998
2241
  if (!stream)
1999
2242
  return reject();
2000
2243
  stream.once(STR_END, () => {
@@ -2003,7 +2246,7 @@ var NodeFsHandler = class {
2003
2246
  return;
2004
2247
  }
2005
2248
  const wasThrottled = throttler ? throttler.clear() : false;
2006
- resolve7(void 0);
2249
+ resolve9(void 0);
2007
2250
  previous.getChildren().filter((item) => {
2008
2251
  return item !== directory && !current.has(item);
2009
2252
  }).forEach((item) => {
@@ -2060,13 +2303,13 @@ var NodeFsHandler = class {
2060
2303
  * @param depth Child path actually targeted for watch
2061
2304
  * @param target Child path actually targeted for watch
2062
2305
  */
2063
- async _addToNodeFs(path11, initialAdd, priorWh, depth, target) {
2306
+ async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2064
2307
  const ready = this.fsw._emitReady;
2065
- if (this.fsw._isIgnored(path11) || this.fsw.closed) {
2308
+ if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2066
2309
  ready();
2067
2310
  return false;
2068
2311
  }
2069
- const wh = this.fsw._getWatchHelpers(path11);
2312
+ const wh = this.fsw._getWatchHelpers(path12);
2070
2313
  if (priorWh) {
2071
2314
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2072
2315
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2082,8 +2325,8 @@ var NodeFsHandler = class {
2082
2325
  const follow = this.fsw.options.followSymlinks;
2083
2326
  let closer;
2084
2327
  if (stats.isDirectory()) {
2085
- const absPath = sp.resolve(path11);
2086
- const targetPath = follow ? await (0, import_promises2.realpath)(path11) : path11;
2328
+ const absPath = sp.resolve(path12);
2329
+ const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2087
2330
  if (this.fsw.closed)
2088
2331
  return;
2089
2332
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2093,29 +2336,29 @@ var NodeFsHandler = class {
2093
2336
  this.fsw._symlinkPaths.set(absPath, targetPath);
2094
2337
  }
2095
2338
  } else if (stats.isSymbolicLink()) {
2096
- const targetPath = follow ? await (0, import_promises2.realpath)(path11) : path11;
2339
+ const targetPath = follow ? await (0, import_promises2.realpath)(path12) : path12;
2097
2340
  if (this.fsw.closed)
2098
2341
  return;
2099
2342
  const parent = sp.dirname(wh.watchPath);
2100
2343
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2101
2344
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2102
- closer = await this._handleDir(parent, stats, initialAdd, depth, path11, wh, targetPath);
2345
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2103
2346
  if (this.fsw.closed)
2104
2347
  return;
2105
2348
  if (targetPath !== void 0) {
2106
- this.fsw._symlinkPaths.set(sp.resolve(path11), targetPath);
2349
+ this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2107
2350
  }
2108
2351
  } else {
2109
2352
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2110
2353
  }
2111
2354
  ready();
2112
2355
  if (closer)
2113
- this.fsw._addPathCloser(path11, closer);
2356
+ this.fsw._addPathCloser(path12, closer);
2114
2357
  return false;
2115
2358
  } catch (error) {
2116
2359
  if (this.fsw._handleError(error)) {
2117
2360
  ready();
2118
- return path11;
2361
+ return path12;
2119
2362
  }
2120
2363
  }
2121
2364
  }
@@ -2147,35 +2390,35 @@ function createPattern(matcher) {
2147
2390
  if (matcher.path === string)
2148
2391
  return true;
2149
2392
  if (matcher.recursive) {
2150
- const relative6 = sp2.relative(matcher.path, string);
2151
- if (!relative6) {
2393
+ const relative8 = sp2.relative(matcher.path, string);
2394
+ if (!relative8) {
2152
2395
  return false;
2153
2396
  }
2154
- return !relative6.startsWith("..") && !sp2.isAbsolute(relative6);
2397
+ return !relative8.startsWith("..") && !sp2.isAbsolute(relative8);
2155
2398
  }
2156
2399
  return false;
2157
2400
  };
2158
2401
  }
2159
2402
  return () => false;
2160
2403
  }
2161
- function normalizePath(path11) {
2162
- if (typeof path11 !== "string")
2404
+ function normalizePath(path12) {
2405
+ if (typeof path12 !== "string")
2163
2406
  throw new Error("string expected");
2164
- path11 = sp2.normalize(path11);
2165
- path11 = path11.replace(/\\/g, "/");
2407
+ path12 = sp2.normalize(path12);
2408
+ path12 = path12.replace(/\\/g, "/");
2166
2409
  let prepend = false;
2167
- if (path11.startsWith("//"))
2410
+ if (path12.startsWith("//"))
2168
2411
  prepend = true;
2169
- path11 = path11.replace(DOUBLE_SLASH_RE, "/");
2412
+ path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2170
2413
  if (prepend)
2171
- path11 = "/" + path11;
2172
- return path11;
2414
+ path12 = "/" + path12;
2415
+ return path12;
2173
2416
  }
2174
2417
  function matchPatterns(patterns, testString, stats) {
2175
- const path11 = normalizePath(testString);
2418
+ const path12 = normalizePath(testString);
2176
2419
  for (let index = 0; index < patterns.length; index++) {
2177
2420
  const pattern = patterns[index];
2178
- if (pattern(path11, stats)) {
2421
+ if (pattern(path12, stats)) {
2179
2422
  return true;
2180
2423
  }
2181
2424
  }
@@ -2213,19 +2456,19 @@ var toUnix = (string) => {
2213
2456
  }
2214
2457
  return str;
2215
2458
  };
2216
- var normalizePathToUnix = (path11) => toUnix(sp2.normalize(toUnix(path11)));
2217
- var normalizeIgnored = (cwd = "") => (path11) => {
2218
- if (typeof path11 === "string") {
2219
- return normalizePathToUnix(sp2.isAbsolute(path11) ? path11 : sp2.join(cwd, path11));
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));
2220
2463
  } else {
2221
- return path11;
2464
+ return path12;
2222
2465
  }
2223
2466
  };
2224
- var getAbsolutePath = (path11, cwd) => {
2225
- if (sp2.isAbsolute(path11)) {
2226
- return path11;
2467
+ var getAbsolutePath = (path12, cwd) => {
2468
+ if (sp2.isAbsolute(path12)) {
2469
+ return path12;
2227
2470
  }
2228
- return sp2.join(cwd, path11);
2471
+ return sp2.join(cwd, path12);
2229
2472
  };
2230
2473
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2231
2474
  var DirEntry = class {
@@ -2290,10 +2533,10 @@ var WatchHelper = class {
2290
2533
  dirParts;
2291
2534
  followSymlinks;
2292
2535
  statMethod;
2293
- constructor(path11, follow, fsw) {
2536
+ constructor(path12, follow, fsw) {
2294
2537
  this.fsw = fsw;
2295
- const watchPath = path11;
2296
- this.path = path11 = path11.replace(REPLACER_RE, "");
2538
+ const watchPath = path12;
2539
+ this.path = path12 = path12.replace(REPLACER_RE, "");
2297
2540
  this.watchPath = watchPath;
2298
2541
  this.fullWatchPath = sp2.resolve(watchPath);
2299
2542
  this.dirParts = [];
@@ -2433,20 +2676,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2433
2676
  this._closePromise = void 0;
2434
2677
  let paths = unifyPaths(paths_);
2435
2678
  if (cwd) {
2436
- paths = paths.map((path11) => {
2437
- const absPath = getAbsolutePath(path11, cwd);
2679
+ paths = paths.map((path12) => {
2680
+ const absPath = getAbsolutePath(path12, cwd);
2438
2681
  return absPath;
2439
2682
  });
2440
2683
  }
2441
- paths.forEach((path11) => {
2442
- this._removeIgnoredPath(path11);
2684
+ paths.forEach((path12) => {
2685
+ this._removeIgnoredPath(path12);
2443
2686
  });
2444
2687
  this._userIgnored = void 0;
2445
2688
  if (!this._readyCount)
2446
2689
  this._readyCount = 0;
2447
2690
  this._readyCount += paths.length;
2448
- Promise.all(paths.map(async (path11) => {
2449
- const res = await this._nodeFsHandler._addToNodeFs(path11, !_internal, void 0, 0, _origAdd);
2691
+ Promise.all(paths.map(async (path12) => {
2692
+ const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2450
2693
  if (res)
2451
2694
  this._emitReady();
2452
2695
  return res;
@@ -2468,17 +2711,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2468
2711
  return this;
2469
2712
  const paths = unifyPaths(paths_);
2470
2713
  const { cwd } = this.options;
2471
- paths.forEach((path11) => {
2472
- if (!sp2.isAbsolute(path11) && !this._closers.has(path11)) {
2714
+ paths.forEach((path12) => {
2715
+ if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2473
2716
  if (cwd)
2474
- path11 = sp2.join(cwd, path11);
2475
- path11 = sp2.resolve(path11);
2717
+ path12 = sp2.join(cwd, path12);
2718
+ path12 = sp2.resolve(path12);
2476
2719
  }
2477
- this._closePath(path11);
2478
- this._addIgnoredPath(path11);
2479
- if (this._watched.has(path11)) {
2720
+ this._closePath(path12);
2721
+ this._addIgnoredPath(path12);
2722
+ if (this._watched.has(path12)) {
2480
2723
  this._addIgnoredPath({
2481
- path: path11,
2724
+ path: path12,
2482
2725
  recursive: true
2483
2726
  });
2484
2727
  }
@@ -2542,38 +2785,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2542
2785
  * @param stats arguments to be passed with event
2543
2786
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2544
2787
  */
2545
- async _emit(event, path11, stats) {
2788
+ async _emit(event, path12, stats) {
2546
2789
  if (this.closed)
2547
2790
  return;
2548
2791
  const opts = this.options;
2549
2792
  if (isWindows)
2550
- path11 = sp2.normalize(path11);
2793
+ path12 = sp2.normalize(path12);
2551
2794
  if (opts.cwd)
2552
- path11 = sp2.relative(opts.cwd, path11);
2553
- const args = [path11];
2795
+ path12 = sp2.relative(opts.cwd, path12);
2796
+ const args = [path12];
2554
2797
  if (stats != null)
2555
2798
  args.push(stats);
2556
2799
  const awf = opts.awaitWriteFinish;
2557
2800
  let pw;
2558
- if (awf && (pw = this._pendingWrites.get(path11))) {
2801
+ if (awf && (pw = this._pendingWrites.get(path12))) {
2559
2802
  pw.lastChange = /* @__PURE__ */ new Date();
2560
2803
  return this;
2561
2804
  }
2562
2805
  if (opts.atomic) {
2563
2806
  if (event === EVENTS.UNLINK) {
2564
- this._pendingUnlinks.set(path11, [event, ...args]);
2807
+ this._pendingUnlinks.set(path12, [event, ...args]);
2565
2808
  setTimeout(() => {
2566
- this._pendingUnlinks.forEach((entry, path12) => {
2809
+ this._pendingUnlinks.forEach((entry, path13) => {
2567
2810
  this.emit(...entry);
2568
2811
  this.emit(EVENTS.ALL, ...entry);
2569
- this._pendingUnlinks.delete(path12);
2812
+ this._pendingUnlinks.delete(path13);
2570
2813
  });
2571
2814
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2572
2815
  return this;
2573
2816
  }
2574
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path11)) {
2817
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2575
2818
  event = EVENTS.CHANGE;
2576
- this._pendingUnlinks.delete(path11);
2819
+ this._pendingUnlinks.delete(path12);
2577
2820
  }
2578
2821
  }
2579
2822
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2591,16 +2834,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2591
2834
  this.emitWithAll(event, args);
2592
2835
  }
2593
2836
  };
2594
- this._awaitWriteFinish(path11, awf.stabilityThreshold, event, awfEmit);
2837
+ this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2595
2838
  return this;
2596
2839
  }
2597
2840
  if (event === EVENTS.CHANGE) {
2598
- const isThrottled = !this._throttle(EVENTS.CHANGE, path11, 50);
2841
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2599
2842
  if (isThrottled)
2600
2843
  return this;
2601
2844
  }
2602
2845
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2603
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path11) : path11;
2846
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path12) : path12;
2604
2847
  let stats2;
2605
2848
  try {
2606
2849
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -2631,23 +2874,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2631
2874
  * @param timeout duration of time to suppress duplicate actions
2632
2875
  * @returns tracking object or false if action should be suppressed
2633
2876
  */
2634
- _throttle(actionType, path11, timeout) {
2877
+ _throttle(actionType, path12, timeout) {
2635
2878
  if (!this._throttled.has(actionType)) {
2636
2879
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2637
2880
  }
2638
2881
  const action = this._throttled.get(actionType);
2639
2882
  if (!action)
2640
2883
  throw new Error("invalid throttle");
2641
- const actionPath = action.get(path11);
2884
+ const actionPath = action.get(path12);
2642
2885
  if (actionPath) {
2643
2886
  actionPath.count++;
2644
2887
  return false;
2645
2888
  }
2646
2889
  let timeoutObject;
2647
2890
  const clear = () => {
2648
- const item = action.get(path11);
2891
+ const item = action.get(path12);
2649
2892
  const count = item ? item.count : 0;
2650
- action.delete(path11);
2893
+ action.delete(path12);
2651
2894
  clearTimeout(timeoutObject);
2652
2895
  if (item)
2653
2896
  clearTimeout(item.timeoutObject);
@@ -2655,7 +2898,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2655
2898
  };
2656
2899
  timeoutObject = setTimeout(clear, timeout);
2657
2900
  const thr = { timeoutObject, clear, count: 0 };
2658
- action.set(path11, thr);
2901
+ action.set(path12, thr);
2659
2902
  return thr;
2660
2903
  }
2661
2904
  _incrReadyCount() {
@@ -2669,44 +2912,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2669
2912
  * @param event
2670
2913
  * @param awfEmit Callback to be called when ready for event to be emitted.
2671
2914
  */
2672
- _awaitWriteFinish(path11, threshold, event, awfEmit) {
2915
+ _awaitWriteFinish(path12, threshold, event, awfEmit) {
2673
2916
  const awf = this.options.awaitWriteFinish;
2674
2917
  if (typeof awf !== "object")
2675
2918
  return;
2676
2919
  const pollInterval = awf.pollInterval;
2677
2920
  let timeoutHandler;
2678
- let fullPath = path11;
2679
- if (this.options.cwd && !sp2.isAbsolute(path11)) {
2680
- fullPath = sp2.join(this.options.cwd, path11);
2921
+ let fullPath = path12;
2922
+ if (this.options.cwd && !sp2.isAbsolute(path12)) {
2923
+ fullPath = sp2.join(this.options.cwd, path12);
2681
2924
  }
2682
2925
  const now = /* @__PURE__ */ new Date();
2683
2926
  const writes = this._pendingWrites;
2684
2927
  function awaitWriteFinishFn(prevStat) {
2685
2928
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
2686
- if (err || !writes.has(path11)) {
2929
+ if (err || !writes.has(path12)) {
2687
2930
  if (err && err.code !== "ENOENT")
2688
2931
  awfEmit(err);
2689
2932
  return;
2690
2933
  }
2691
2934
  const now2 = Number(/* @__PURE__ */ new Date());
2692
2935
  if (prevStat && curStat.size !== prevStat.size) {
2693
- writes.get(path11).lastChange = now2;
2936
+ writes.get(path12).lastChange = now2;
2694
2937
  }
2695
- const pw = writes.get(path11);
2938
+ const pw = writes.get(path12);
2696
2939
  const df = now2 - pw.lastChange;
2697
2940
  if (df >= threshold) {
2698
- writes.delete(path11);
2941
+ writes.delete(path12);
2699
2942
  awfEmit(void 0, curStat);
2700
2943
  } else {
2701
2944
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2702
2945
  }
2703
2946
  });
2704
2947
  }
2705
- if (!writes.has(path11)) {
2706
- writes.set(path11, {
2948
+ if (!writes.has(path12)) {
2949
+ writes.set(path12, {
2707
2950
  lastChange: now,
2708
2951
  cancelWait: () => {
2709
- writes.delete(path11);
2952
+ writes.delete(path12);
2710
2953
  clearTimeout(timeoutHandler);
2711
2954
  return event;
2712
2955
  }
@@ -2717,8 +2960,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2717
2960
  /**
2718
2961
  * Determines whether user has asked to ignore this path.
2719
2962
  */
2720
- _isIgnored(path11, stats) {
2721
- if (this.options.atomic && DOT_RE.test(path11))
2963
+ _isIgnored(path12, stats) {
2964
+ if (this.options.atomic && DOT_RE.test(path12))
2722
2965
  return true;
2723
2966
  if (!this._userIgnored) {
2724
2967
  const { cwd } = this.options;
@@ -2728,17 +2971,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2728
2971
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2729
2972
  this._userIgnored = anymatch(list, void 0);
2730
2973
  }
2731
- return this._userIgnored(path11, stats);
2974
+ return this._userIgnored(path12, stats);
2732
2975
  }
2733
- _isntIgnored(path11, stat4) {
2734
- return !this._isIgnored(path11, stat4);
2976
+ _isntIgnored(path12, stat4) {
2977
+ return !this._isIgnored(path12, stat4);
2735
2978
  }
2736
2979
  /**
2737
2980
  * Provides a set of common helpers and properties relating to symlink handling.
2738
2981
  * @param path file or directory pattern being watched
2739
2982
  */
2740
- _getWatchHelpers(path11) {
2741
- return new WatchHelper(path11, this.options.followSymlinks, this);
2983
+ _getWatchHelpers(path12) {
2984
+ return new WatchHelper(path12, this.options.followSymlinks, this);
2742
2985
  }
2743
2986
  // Directory helpers
2744
2987
  // -----------------
@@ -2770,63 +3013,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
2770
3013
  * @param item base path of item/directory
2771
3014
  */
2772
3015
  _remove(directory, item, isDirectory) {
2773
- const path11 = sp2.join(directory, item);
2774
- const fullPath = sp2.resolve(path11);
2775
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path11) || this._watched.has(fullPath);
2776
- if (!this._throttle("remove", path11, 100))
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))
2777
3020
  return;
2778
3021
  if (!isDirectory && this._watched.size === 1) {
2779
3022
  this.add(directory, item, true);
2780
3023
  }
2781
- const wp = this._getWatchedDir(path11);
3024
+ const wp = this._getWatchedDir(path12);
2782
3025
  const nestedDirectoryChildren = wp.getChildren();
2783
- nestedDirectoryChildren.forEach((nested) => this._remove(path11, nested));
3026
+ nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
2784
3027
  const parent = this._getWatchedDir(directory);
2785
3028
  const wasTracked = parent.has(item);
2786
3029
  parent.remove(item);
2787
3030
  if (this._symlinkPaths.has(fullPath)) {
2788
3031
  this._symlinkPaths.delete(fullPath);
2789
3032
  }
2790
- let relPath = path11;
3033
+ let relPath = path12;
2791
3034
  if (this.options.cwd)
2792
- relPath = sp2.relative(this.options.cwd, path11);
3035
+ relPath = sp2.relative(this.options.cwd, path12);
2793
3036
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
2794
3037
  const event = this._pendingWrites.get(relPath).cancelWait();
2795
3038
  if (event === EVENTS.ADD)
2796
3039
  return;
2797
3040
  }
2798
- this._watched.delete(path11);
3041
+ this._watched.delete(path12);
2799
3042
  this._watched.delete(fullPath);
2800
3043
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
2801
- if (wasTracked && !this._isIgnored(path11))
2802
- this._emit(eventName, path11);
2803
- this._closePath(path11);
3044
+ if (wasTracked && !this._isIgnored(path12))
3045
+ this._emit(eventName, path12);
3046
+ this._closePath(path12);
2804
3047
  }
2805
3048
  /**
2806
3049
  * Closes all watchers for a path
2807
3050
  */
2808
- _closePath(path11) {
2809
- this._closeFile(path11);
2810
- const dir = sp2.dirname(path11);
2811
- this._getWatchedDir(dir).remove(sp2.basename(path11));
3051
+ _closePath(path12) {
3052
+ this._closeFile(path12);
3053
+ const dir = sp2.dirname(path12);
3054
+ this._getWatchedDir(dir).remove(sp2.basename(path12));
2812
3055
  }
2813
3056
  /**
2814
3057
  * Closes only file-specific watchers
2815
3058
  */
2816
- _closeFile(path11) {
2817
- const closers = this._closers.get(path11);
3059
+ _closeFile(path12) {
3060
+ const closers = this._closers.get(path12);
2818
3061
  if (!closers)
2819
3062
  return;
2820
3063
  closers.forEach((closer) => closer());
2821
- this._closers.delete(path11);
3064
+ this._closers.delete(path12);
2822
3065
  }
2823
- _addPathCloser(path11, closer) {
3066
+ _addPathCloser(path12, closer) {
2824
3067
  if (!closer)
2825
3068
  return;
2826
- let list = this._closers.get(path11);
3069
+ let list = this._closers.get(path12);
2827
3070
  if (!list) {
2828
3071
  list = [];
2829
- this._closers.set(path11, list);
3072
+ this._closers.set(path12, list);
2830
3073
  }
2831
3074
  list.push(closer);
2832
3075
  }
@@ -2856,12 +3099,12 @@ function watch(paths, options = {}) {
2856
3099
  var chokidar_default = { watch, FSWatcher };
2857
3100
 
2858
3101
  // src/watcher/index.ts
2859
- var path4 = __toESM(require("path"), 1);
3102
+ var path5 = __toESM(require("path"), 1);
2860
3103
 
2861
3104
  // src/utils/files.ts
2862
3105
  var import_ignore = __toESM(require_ignore(), 1);
2863
- var import_fs2 = require("fs");
2864
- var path2 = __toESM(require("path"), 1);
3106
+ var import_fs4 = require("fs");
3107
+ var path4 = __toESM(require("path"), 1);
2865
3108
  var PROJECT_MARKERS = [
2866
3109
  ".git",
2867
3110
  "package.json",
@@ -2880,7 +3123,7 @@ var PROJECT_MARKERS = [
2880
3123
  ];
2881
3124
  function hasProjectMarker(projectRoot) {
2882
3125
  for (const marker of PROJECT_MARKERS) {
2883
- if ((0, import_fs2.existsSync)(path2.join(projectRoot, marker))) {
3126
+ if ((0, import_fs4.existsSync)(path4.join(projectRoot, marker))) {
2884
3127
  return true;
2885
3128
  }
2886
3129
  }
@@ -2906,16 +3149,16 @@ function createIgnoreFilter(projectRoot) {
2906
3149
  "**/*build*/**"
2907
3150
  ];
2908
3151
  ig.add(defaultIgnores);
2909
- const gitignorePath = path2.join(projectRoot, ".gitignore");
2910
- if ((0, import_fs2.existsSync)(gitignorePath)) {
2911
- const gitignoreContent = (0, import_fs2.readFileSync)(gitignorePath, "utf-8");
3152
+ const gitignorePath = path4.join(projectRoot, ".gitignore");
3153
+ if ((0, import_fs4.existsSync)(gitignorePath)) {
3154
+ const gitignoreContent = (0, import_fs4.readFileSync)(gitignorePath, "utf-8");
2912
3155
  ig.add(gitignoreContent);
2913
3156
  }
2914
3157
  return ig;
2915
3158
  }
2916
3159
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
2917
- const relativePath = path2.relative(projectRoot, filePath);
2918
- const pathParts = relativePath.split(path2.sep);
3160
+ const relativePath = path4.relative(projectRoot, filePath);
3161
+ const pathParts = relativePath.split(path4.sep);
2919
3162
  for (const part of pathParts) {
2920
3163
  if (part.startsWith(".") && part !== "." && part !== "..") {
2921
3164
  return false;
@@ -2955,12 +3198,12 @@ function matchGlob(filePath, pattern) {
2955
3198
  return regex.test(filePath);
2956
3199
  }
2957
3200
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
2958
- const entries = await import_fs2.promises.readdir(dir, { withFileTypes: true });
3201
+ const entries = await import_fs4.promises.readdir(dir, { withFileTypes: true });
2959
3202
  const filesInDir = [];
2960
3203
  const subdirs = [];
2961
3204
  for (const entry of entries) {
2962
- const fullPath = path2.join(dir, entry.name);
2963
- const relativePath = path2.relative(projectRoot, fullPath);
3205
+ const fullPath = path4.join(dir, entry.name);
3206
+ const relativePath = path4.relative(projectRoot, fullPath);
2964
3207
  if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
2965
3208
  if (entry.isDirectory()) {
2966
3209
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -2980,7 +3223,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
2980
3223
  if (entry.isDirectory()) {
2981
3224
  subdirs.push({ fullPath, relativePath });
2982
3225
  } else if (entry.isFile()) {
2983
- const stat4 = await import_fs2.promises.stat(fullPath);
3226
+ const stat4 = await import_fs4.promises.stat(fullPath);
2984
3227
  if (stat4.size > maxFileSize) {
2985
3228
  skipped.push({ path: relativePath, reason: "too_large" });
2986
3229
  continue;
@@ -3009,7 +3252,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3009
3252
  yield f;
3010
3253
  }
3011
3254
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3012
- skipped.push({ path: path2.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3255
+ skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3013
3256
  }
3014
3257
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3015
3258
  if (canRecurse) {
@@ -3049,14 +3292,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3049
3292
  if (additionalRoots && additionalRoots.length > 0) {
3050
3293
  const normalizedRoots = /* @__PURE__ */ new Set();
3051
3294
  for (const kbRoot of additionalRoots) {
3052
- const resolved = path2.normalize(
3053
- path2.isAbsolute(kbRoot) ? kbRoot : path2.resolve(projectRoot, kbRoot)
3295
+ const resolved = path4.normalize(
3296
+ path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3054
3297
  );
3055
3298
  normalizedRoots.add(resolved);
3056
3299
  }
3057
3300
  for (const resolvedKbRoot of normalizedRoots) {
3058
3301
  try {
3059
- const stat4 = await import_fs2.promises.stat(resolvedKbRoot);
3302
+ const stat4 = await import_fs4.promises.stat(resolvedKbRoot);
3060
3303
  if (!stat4.isDirectory()) {
3061
3304
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3062
3305
  continue;
@@ -3083,122 +3326,6 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3083
3326
  return { files, skipped };
3084
3327
  }
3085
3328
 
3086
- // src/git/index.ts
3087
- var import_fs3 = require("fs");
3088
- var path3 = __toESM(require("path"), 1);
3089
- function readPackedRefs(gitDir) {
3090
- const packedRefsPath = path3.join(gitDir, "packed-refs");
3091
- if (!(0, import_fs3.existsSync)(packedRefsPath)) {
3092
- return [];
3093
- }
3094
- try {
3095
- return (0, import_fs3.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
3096
- } catch {
3097
- return [];
3098
- }
3099
- }
3100
- function resolveCommonGitDir(gitDir) {
3101
- const commonDirPath = path3.join(gitDir, "commondir");
3102
- if (!(0, import_fs3.existsSync)(commonDirPath)) {
3103
- return gitDir;
3104
- }
3105
- try {
3106
- const raw = (0, import_fs3.readFileSync)(commonDirPath, "utf-8").trim();
3107
- if (!raw) {
3108
- return gitDir;
3109
- }
3110
- const resolved = path3.isAbsolute(raw) ? raw : path3.resolve(gitDir, raw);
3111
- if ((0, import_fs3.existsSync)(resolved)) {
3112
- return resolved;
3113
- }
3114
- } catch {
3115
- return gitDir;
3116
- }
3117
- return gitDir;
3118
- }
3119
- function resolveGitDir(repoRoot) {
3120
- const gitPath = path3.join(repoRoot, ".git");
3121
- if (!(0, import_fs3.existsSync)(gitPath)) {
3122
- return null;
3123
- }
3124
- try {
3125
- const stat4 = (0, import_fs3.statSync)(gitPath);
3126
- if (stat4.isDirectory()) {
3127
- return gitPath;
3128
- }
3129
- if (stat4.isFile()) {
3130
- const content = (0, import_fs3.readFileSync)(gitPath, "utf-8").trim();
3131
- const match = content.match(/^gitdir:\s*(.+)$/);
3132
- if (match) {
3133
- const gitdir = match[1];
3134
- const resolvedPath = path3.isAbsolute(gitdir) ? gitdir : path3.resolve(repoRoot, gitdir);
3135
- if ((0, import_fs3.existsSync)(resolvedPath)) {
3136
- return resolvedPath;
3137
- }
3138
- }
3139
- }
3140
- } catch {
3141
- }
3142
- return null;
3143
- }
3144
- function isGitRepo(dir) {
3145
- return resolveGitDir(dir) !== null;
3146
- }
3147
- function getCurrentBranch(repoRoot) {
3148
- const gitDir = resolveGitDir(repoRoot);
3149
- if (!gitDir) {
3150
- return null;
3151
- }
3152
- const headPath = path3.join(gitDir, "HEAD");
3153
- if (!(0, import_fs3.existsSync)(headPath)) {
3154
- return null;
3155
- }
3156
- try {
3157
- const headContent = (0, import_fs3.readFileSync)(headPath, "utf-8").trim();
3158
- const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
3159
- if (match) {
3160
- return match[1];
3161
- }
3162
- if (/^[0-9a-f]{40}$/i.test(headContent)) {
3163
- return headContent.slice(0, 7);
3164
- }
3165
- return null;
3166
- } catch {
3167
- return null;
3168
- }
3169
- }
3170
- function getBaseBranch(repoRoot) {
3171
- const gitDir = resolveGitDir(repoRoot);
3172
- const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
3173
- const candidates = ["main", "master", "develop", "trunk"];
3174
- if (refStoreDir) {
3175
- for (const candidate of candidates) {
3176
- const refPath = path3.join(refStoreDir, "refs", "heads", candidate);
3177
- if ((0, import_fs3.existsSync)(refPath)) {
3178
- return candidate;
3179
- }
3180
- const packedRefs = readPackedRefs(refStoreDir);
3181
- if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
3182
- return candidate;
3183
- }
3184
- }
3185
- }
3186
- return getCurrentBranch(repoRoot) ?? "main";
3187
- }
3188
- function getBranchOrDefault(repoRoot) {
3189
- if (!isGitRepo(repoRoot)) {
3190
- return "default";
3191
- }
3192
- return getCurrentBranch(repoRoot) ?? "default";
3193
- }
3194
- function getHeadPath(repoRoot) {
3195
- const gitDir = resolveGitDir(repoRoot);
3196
- if (gitDir) {
3197
- return path3.join(gitDir, "HEAD");
3198
- }
3199
- return path3.join(repoRoot, ".git", "HEAD");
3200
- }
3201
-
3202
3329
  // src/watcher/index.ts
3203
3330
  var FileWatcher = class {
3204
3331
  watcher = null;
@@ -3220,9 +3347,9 @@ var FileWatcher = class {
3220
3347
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3221
3348
  this.watcher = chokidar_default.watch(this.projectRoot, {
3222
3349
  ignored: (filePath) => {
3223
- const relativePath = path4.relative(this.projectRoot, filePath);
3350
+ const relativePath = path5.relative(this.projectRoot, filePath);
3224
3351
  if (!relativePath) return false;
3225
- const pathParts = relativePath.split(path4.sep);
3352
+ const pathParts = relativePath.split(path5.sep);
3226
3353
  for (const part of pathParts) {
3227
3354
  if (part.startsWith(".") && part !== "." && part !== "..") {
3228
3355
  return true;
@@ -3274,7 +3401,7 @@ var FileWatcher = class {
3274
3401
  return;
3275
3402
  }
3276
3403
  const changes = Array.from(this.pendingChanges.entries()).map(
3277
- ([path11, type]) => ({ path: path11, type })
3404
+ ([path12, type]) => ({ path: path12, type })
3278
3405
  );
3279
3406
  this.pendingChanges.clear();
3280
3407
  try {
@@ -3320,7 +3447,7 @@ var GitHeadWatcher = class {
3320
3447
  this.onBranchChange = handler;
3321
3448
  this.currentBranch = getCurrentBranch(this.projectRoot);
3322
3449
  const headPath = getHeadPath(this.projectRoot);
3323
- const refsPath = path4.join(this.projectRoot, ".git", "refs", "heads");
3450
+ const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3324
3451
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3325
3452
  persistent: true,
3326
3453
  ignoreInitial: true,
@@ -3405,8 +3532,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3405
3532
  var import_plugin = require("@opencode-ai/plugin");
3406
3533
 
3407
3534
  // src/indexer/index.ts
3408
- var import_fs5 = require("fs");
3409
- var path7 = __toESM(require("path"), 1);
3535
+ var import_fs6 = require("fs");
3536
+ var path8 = __toESM(require("path"), 1);
3410
3537
  var import_perf_hooks = require("perf_hooks");
3411
3538
 
3412
3539
  // node_modules/eventemitter3/index.mjs
@@ -3431,7 +3558,7 @@ function pTimeout(promise, options) {
3431
3558
  } = options;
3432
3559
  let timer;
3433
3560
  let abortHandler;
3434
- const wrappedPromise = new Promise((resolve7, reject) => {
3561
+ const wrappedPromise = new Promise((resolve9, reject) => {
3435
3562
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3436
3563
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3437
3564
  }
@@ -3445,7 +3572,7 @@ function pTimeout(promise, options) {
3445
3572
  };
3446
3573
  signal.addEventListener("abort", abortHandler, { once: true });
3447
3574
  }
3448
- promise.then(resolve7, reject);
3575
+ promise.then(resolve9, reject);
3449
3576
  if (milliseconds === Number.POSITIVE_INFINITY) {
3450
3577
  return;
3451
3578
  }
@@ -3453,7 +3580,7 @@ function pTimeout(promise, options) {
3453
3580
  timer = customTimers.setTimeout.call(void 0, () => {
3454
3581
  if (fallback) {
3455
3582
  try {
3456
- resolve7(fallback());
3583
+ resolve9(fallback());
3457
3584
  } catch (error) {
3458
3585
  reject(error);
3459
3586
  }
@@ -3463,7 +3590,7 @@ function pTimeout(promise, options) {
3463
3590
  promise.cancel();
3464
3591
  }
3465
3592
  if (message === false) {
3466
- resolve7();
3593
+ resolve9();
3467
3594
  } else if (message instanceof Error) {
3468
3595
  reject(message);
3469
3596
  } else {
@@ -3865,7 +3992,7 @@ var PQueue = class extends import_index.default {
3865
3992
  // Assign unique ID if not provided
3866
3993
  id: options.id ?? (this.#idAssigner++).toString()
3867
3994
  };
3868
- return new Promise((resolve7, reject) => {
3995
+ return new Promise((resolve9, reject) => {
3869
3996
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3870
3997
  let cleanupQueueAbortHandler = () => void 0;
3871
3998
  const run = async () => {
@@ -3905,7 +4032,7 @@ var PQueue = class extends import_index.default {
3905
4032
  })]);
3906
4033
  }
3907
4034
  const result = await operation;
3908
- resolve7(result);
4035
+ resolve9(result);
3909
4036
  this.emit("completed", result);
3910
4037
  } catch (error) {
3911
4038
  reject(error);
@@ -4093,13 +4220,13 @@ var PQueue = class extends import_index.default {
4093
4220
  });
4094
4221
  }
4095
4222
  async #onEvent(event, filter) {
4096
- return new Promise((resolve7) => {
4223
+ return new Promise((resolve9) => {
4097
4224
  const listener = () => {
4098
4225
  if (filter && !filter()) {
4099
4226
  return;
4100
4227
  }
4101
4228
  this.off(event, listener);
4102
- resolve7();
4229
+ resolve9();
4103
4230
  };
4104
4231
  this.on(event, listener);
4105
4232
  });
@@ -4385,7 +4512,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4385
4512
  const finalDelay = Math.min(delayTime, remainingTime);
4386
4513
  options.signal?.throwIfAborted();
4387
4514
  if (finalDelay > 0) {
4388
- await new Promise((resolve7, reject) => {
4515
+ await new Promise((resolve9, reject) => {
4389
4516
  const onAbort = () => {
4390
4517
  clearTimeout(timeoutToken);
4391
4518
  options.signal?.removeEventListener("abort", onAbort);
@@ -4393,7 +4520,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4393
4520
  };
4394
4521
  const timeoutToken = setTimeout(() => {
4395
4522
  options.signal?.removeEventListener("abort", onAbort);
4396
- resolve7();
4523
+ resolve9();
4397
4524
  }, finalDelay);
4398
4525
  if (options.unref) {
4399
4526
  timeoutToken.unref?.();
@@ -4454,17 +4581,17 @@ async function pRetry(input, options = {}) {
4454
4581
  }
4455
4582
 
4456
4583
  // src/embeddings/detector.ts
4457
- var import_fs4 = require("fs");
4458
- var path5 = __toESM(require("path"), 1);
4459
- var os2 = __toESM(require("os"), 1);
4584
+ var import_fs5 = require("fs");
4585
+ var path6 = __toESM(require("path"), 1);
4586
+ var os3 = __toESM(require("os"), 1);
4460
4587
  function getOpenCodeAuthPath() {
4461
- return path5.join(os2.homedir(), ".local", "share", "opencode", "auth.json");
4588
+ return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4462
4589
  }
4463
4590
  function loadOpenCodeAuth() {
4464
4591
  const authPath = getOpenCodeAuthPath();
4465
4592
  try {
4466
- if ((0, import_fs4.existsSync)(authPath)) {
4467
- return JSON.parse((0, import_fs4.readFileSync)(authPath, "utf-8"));
4593
+ if ((0, import_fs5.existsSync)(authPath)) {
4594
+ return JSON.parse((0, import_fs5.readFileSync)(authPath, "utf-8"));
4468
4595
  }
4469
4596
  } catch {
4470
4597
  }
@@ -4497,7 +4624,7 @@ async function detectEmbeddingProvider(preferredProvider, model) {
4497
4624
  );
4498
4625
  }
4499
4626
  async function tryDetectProvider() {
4500
- for (const provider of availableProviders) {
4627
+ for (const provider of autoDetectProviders) {
4501
4628
  const credentials = await getProviderCredentials(provider);
4502
4629
  if (credentials) {
4503
4630
  return {
@@ -4508,7 +4635,7 @@ async function tryDetectProvider() {
4508
4635
  }
4509
4636
  }
4510
4637
  throw new Error(
4511
- `No embedding-capable provider found. Please authenticate with OpenCode using one of: ${availableProviders.join(", ")}.`
4638
+ `No embedding-capable provider found. Please authenticate with OpenCode using one of: ${autoDetectProviders.join(", ")}.`
4512
4639
  );
4513
4640
  }
4514
4641
  async function getProviderCredentials(provider) {
@@ -4828,11 +4955,12 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4828
4955
  return this.modelInfo;
4829
4956
  }
4830
4957
  };
4831
- var OllamaEmbeddingProvider = class {
4958
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4832
4959
  constructor(credentials, modelInfo) {
4833
4960
  this.credentials = credentials;
4834
4961
  this.modelInfo = modelInfo;
4835
4962
  }
4963
+ static MIN_TRUNCATION_CHARS = 512;
4836
4964
  async embedQuery(query) {
4837
4965
  const result = await this.embedBatch([query]);
4838
4966
  return {
@@ -4847,30 +4975,103 @@ var OllamaEmbeddingProvider = class {
4847
4975
  tokensUsed: result.totalTokensUsed
4848
4976
  };
4849
4977
  }
4850
- async embedBatch(texts) {
4851
- const results = await Promise.all(
4852
- texts.map(async (text) => {
4853
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
4854
- method: "POST",
4855
- headers: {
4856
- "Content-Type": "application/json"
4857
- },
4858
- body: JSON.stringify({
4859
- model: this.modelInfo.model,
4860
- prompt: text
4861
- })
4862
- });
4863
- if (!response.ok) {
4864
- const error = await response.text();
4865
- throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
4978
+ estimateTokens(text) {
4979
+ return Math.ceil(text.length / 4);
4980
+ }
4981
+ truncateToCharLimit(text, maxChars) {
4982
+ if (text.length <= maxChars) {
4983
+ return text;
4984
+ }
4985
+ return `${text.slice(0, Math.max(0, maxChars - 17))}
4986
+ ... [truncated]`;
4987
+ }
4988
+ isContextLengthError(error) {
4989
+ const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
4990
+ return message.includes("context length") && (message.includes("exceed") || message.includes("exceeded") || message.includes("too long")) || message.includes("input length exceeds the context length") || message.includes("context length exceeded");
4991
+ }
4992
+ buildTruncationCandidates(text) {
4993
+ const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
4994
+ const candidateLimits = /* @__PURE__ */ new Set();
4995
+ const baselineLimit = text.length > baseMaxChars ? baseMaxChars : Math.max(
4996
+ _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
4997
+ Math.floor(text.length * 0.9)
4998
+ );
4999
+ if (baselineLimit < text.length) {
5000
+ candidateLimits.add(baselineLimit);
5001
+ }
5002
+ for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
5003
+ const scaledLimit = Math.max(
5004
+ _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
5005
+ Math.floor(baselineLimit * factor)
5006
+ );
5007
+ if (scaledLimit < text.length) {
5008
+ candidateLimits.add(scaledLimit);
5009
+ }
5010
+ }
5011
+ candidateLimits.add(Math.min(text.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
5012
+ const candidates = [];
5013
+ const seen = /* @__PURE__ */ new Set();
5014
+ for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
5015
+ if (limit <= 0 || limit >= text.length) {
5016
+ continue;
5017
+ }
5018
+ const truncated = this.truncateToCharLimit(text, limit);
5019
+ if (truncated === text || seen.has(truncated)) {
5020
+ continue;
5021
+ }
5022
+ seen.add(truncated);
5023
+ candidates.push(truncated);
5024
+ }
5025
+ return candidates;
5026
+ }
5027
+ async embedSingleWithFallback(text) {
5028
+ try {
5029
+ return await this.embedSingle(text);
5030
+ } catch (error) {
5031
+ if (!this.isContextLengthError(error)) {
5032
+ throw error;
5033
+ }
5034
+ let lastError = error;
5035
+ for (const truncated of this.buildTruncationCandidates(text)) {
5036
+ try {
5037
+ return await this.embedSingle(truncated);
5038
+ } catch (retryError) {
5039
+ if (!this.isContextLengthError(retryError)) {
5040
+ throw retryError;
5041
+ }
5042
+ lastError = retryError;
4866
5043
  }
4867
- const data = await response.json();
4868
- return {
4869
- embedding: data.embedding,
4870
- tokensUsed: Math.ceil(text.length / 4)
4871
- };
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
4872
5058
  })
4873
- );
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
+ }
4874
5075
  return {
4875
5076
  embeddings: results.map((r) => r.embedding),
4876
5077
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
@@ -5426,14 +5627,14 @@ function initializeLogger(config) {
5426
5627
  }
5427
5628
 
5428
5629
  // src/native/index.ts
5429
- var path6 = __toESM(require("path"), 1);
5430
- var os3 = __toESM(require("os"), 1);
5630
+ var path7 = __toESM(require("path"), 1);
5631
+ var os4 = __toESM(require("os"), 1);
5431
5632
  var module2 = __toESM(require("module"), 1);
5432
5633
  var import_url = require("url");
5433
5634
  var import_meta = {};
5434
5635
  function getNativeBinding() {
5435
- const platform2 = os3.platform();
5436
- const arch2 = os3.arch();
5636
+ const platform2 = os4.platform();
5637
+ const arch2 = os4.arch();
5437
5638
  let bindingName;
5438
5639
  if (platform2 === "darwin" && arch2 === "arm64") {
5439
5640
  bindingName = "codebase-index-native.darwin-arm64.node";
@@ -5451,18 +5652,19 @@ function getNativeBinding() {
5451
5652
  let currentDir;
5452
5653
  let requireTarget;
5453
5654
  if (typeof import_meta !== "undefined" && import_meta.url) {
5454
- currentDir = path6.dirname((0, import_url.fileURLToPath)(import_meta.url));
5655
+ currentDir = path7.dirname((0, import_url.fileURLToPath)(import_meta.url));
5455
5656
  requireTarget = import_meta.url;
5456
5657
  } else if (typeof __dirname !== "undefined") {
5457
5658
  currentDir = __dirname;
5458
5659
  requireTarget = __filename;
5459
5660
  } else {
5460
5661
  currentDir = process.cwd();
5461
- requireTarget = path6.join(currentDir, "index.js");
5662
+ requireTarget = path7.join(currentDir, "index.js");
5462
5663
  }
5463
- const isDevMode = currentDir.includes("/src/native");
5464
- const packageRoot = isDevMode ? path6.resolve(currentDir, "../..") : path6.resolve(currentDir, "..");
5465
- const nativePath = path6.join(packageRoot, "native", bindingName);
5664
+ 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);
5466
5668
  const require2 = module2.createRequire(requireTarget);
5467
5669
  return require2(nativePath);
5468
5670
  }
@@ -5493,11 +5695,20 @@ function createMockNativeBinding() {
5493
5695
  constructor() {
5494
5696
  throw error;
5495
5697
  }
5698
+ serialize() {
5699
+ throw error;
5700
+ }
5701
+ deserialize() {
5702
+ throw error;
5703
+ }
5496
5704
  },
5497
5705
  Database: class {
5498
5706
  constructor() {
5499
5707
  throw error;
5500
5708
  }
5709
+ close() {
5710
+ throw error;
5711
+ }
5501
5712
  }
5502
5713
  };
5503
5714
  }
@@ -5630,7 +5841,7 @@ var MAX_SINGLE_CHUNK_TOKENS = 2e3;
5630
5841
  function estimateTokens(text) {
5631
5842
  return Math.ceil(text.length / CHARS_PER_TOKEN);
5632
5843
  }
5633
- function createEmbeddingText(chunk, filePath) {
5844
+ function getEmbeddingHeaderParts(chunk, filePath) {
5634
5845
  const parts = [];
5635
5846
  const fileName = filePath.split("/").pop() || filePath;
5636
5847
  const dirPath = filePath.split("/").slice(-3, -1).join("/");
@@ -5677,23 +5888,52 @@ function createEmbeddingText(chunk, filePath) {
5677
5888
  if (semanticHints.length > 0) {
5678
5889
  parts.push(`Purpose: ${semanticHints.join(", ")}`);
5679
5890
  }
5680
- parts.push("");
5681
- let content = chunk.content;
5682
- const headerLength = parts.join("\n").length;
5683
- const maxContentChars = MAX_SINGLE_CHUNK_TOKENS * CHARS_PER_TOKEN - headerLength;
5684
- if (content.length > maxContentChars) {
5685
- content = content.slice(0, maxContentChars) + "\n... [truncated]";
5891
+ return parts;
5892
+ }
5893
+ function buildEmbeddingText(headerParts, content, partIndex, partCount) {
5894
+ const parts = [...headerParts];
5895
+ if (partCount && partCount > 1 && partIndex) {
5896
+ parts.push(`Part ${partIndex}/${partCount}`);
5686
5897
  }
5898
+ parts.push("");
5687
5899
  parts.push(content);
5688
5900
  return parts.join("\n");
5689
5901
  }
5690
- function createDynamicBatches(chunks) {
5902
+ function splitOversizedContent(content, maxContentChars) {
5903
+ if (content.length <= maxContentChars) {
5904
+ return [content];
5905
+ }
5906
+ const overlapChars = Math.max(CHARS_PER_TOKEN * 32, Math.min(Math.floor(maxContentChars * 0.15), CHARS_PER_TOKEN * 128));
5907
+ const stepChars = Math.max(1, maxContentChars - overlapChars);
5908
+ const segments = [];
5909
+ for (let start = 0; start < content.length; start += stepChars) {
5910
+ const end = Math.min(content.length, start + maxContentChars);
5911
+ segments.push(content.slice(start, end));
5912
+ if (end >= content.length) {
5913
+ break;
5914
+ }
5915
+ }
5916
+ return segments;
5917
+ }
5918
+ function createEmbeddingTexts(chunk, filePath, maxChunkTokens = MAX_SINGLE_CHUNK_TOKENS) {
5919
+ const headerParts = getEmbeddingHeaderParts(chunk, filePath);
5920
+ const headerLength = buildEmbeddingText(headerParts, "", 1, 9).length;
5921
+ const maxContentChars = Math.max(1, maxChunkTokens * CHARS_PER_TOKEN - headerLength);
5922
+ const segments = splitOversizedContent(chunk.content, maxContentChars);
5923
+ if (segments.length === 1) {
5924
+ return [buildEmbeddingText(headerParts, segments[0])];
5925
+ }
5926
+ return segments.map((segment, index) => buildEmbeddingText(headerParts, segment, index + 1, segments.length));
5927
+ }
5928
+ function createDynamicBatches(chunks, options = {}) {
5691
5929
  const batches = [];
5692
5930
  let currentBatch = [];
5693
5931
  let currentTokens = 0;
5932
+ const maxBatchTokens = Math.max(1, options.maxBatchTokens ?? MAX_BATCH_TOKENS);
5933
+ const maxBatchItems = Math.max(1, options.maxBatchItems ?? Number.MAX_SAFE_INTEGER);
5694
5934
  for (const chunk of chunks) {
5695
- const chunkTokens = estimateTokens(chunk.text);
5696
- if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
5935
+ const chunkTokens = chunk.tokenCount ?? estimateTokens(chunk.text);
5936
+ if (currentBatch.length > 0 && (currentTokens + chunkTokens > maxBatchTokens || currentBatch.length >= maxBatchItems)) {
5697
5937
  batches.push(currentBatch);
5698
5938
  currentBatch = [];
5699
5939
  currentTokens = 0;
@@ -5812,6 +6052,12 @@ var InvertedIndex = class {
5812
6052
  save() {
5813
6053
  this.inner.save();
5814
6054
  }
6055
+ serialize() {
6056
+ return this.inner.serialize();
6057
+ }
6058
+ deserialize(json) {
6059
+ this.inner.deserialize(json);
6060
+ }
5815
6061
  addChunk(chunkId, content) {
5816
6062
  this.inner.addChunk(chunkId, content);
5817
6063
  }
@@ -5838,158 +6084,263 @@ var InvertedIndex = class {
5838
6084
  };
5839
6085
  var Database = class {
5840
6086
  inner;
6087
+ closed = false;
5841
6088
  constructor(dbPath) {
5842
6089
  this.inner = new native.Database(dbPath);
5843
6090
  }
6091
+ throwIfClosed() {
6092
+ if (this.closed) {
6093
+ throw new Error("Database is closed");
6094
+ }
6095
+ }
6096
+ close() {
6097
+ if (this.closed) {
6098
+ return;
6099
+ }
6100
+ if (typeof this.inner.close === "function") {
6101
+ this.inner.close();
6102
+ }
6103
+ this.closed = true;
6104
+ }
5844
6105
  embeddingExists(contentHash) {
6106
+ this.throwIfClosed();
5845
6107
  return this.inner.embeddingExists(contentHash);
5846
6108
  }
5847
6109
  getEmbedding(contentHash) {
6110
+ this.throwIfClosed();
5848
6111
  return this.inner.getEmbedding(contentHash) ?? null;
5849
6112
  }
5850
6113
  upsertEmbedding(contentHash, embedding, chunkText, model) {
6114
+ this.throwIfClosed();
5851
6115
  this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
5852
6116
  }
5853
6117
  upsertEmbeddingsBatch(items) {
6118
+ this.throwIfClosed();
5854
6119
  if (items.length === 0) return;
5855
6120
  this.inner.upsertEmbeddingsBatch(items);
5856
6121
  }
5857
6122
  getMissingEmbeddings(contentHashes) {
6123
+ this.throwIfClosed();
5858
6124
  return this.inner.getMissingEmbeddings(contentHashes);
5859
6125
  }
5860
6126
  upsertChunk(chunk) {
6127
+ this.throwIfClosed();
5861
6128
  this.inner.upsertChunk(chunk);
5862
6129
  }
5863
6130
  upsertChunksBatch(chunks) {
6131
+ this.throwIfClosed();
5864
6132
  if (chunks.length === 0) return;
5865
6133
  this.inner.upsertChunksBatch(chunks);
5866
6134
  }
5867
6135
  getChunk(chunkId) {
6136
+ this.throwIfClosed();
5868
6137
  return this.inner.getChunk(chunkId) ?? null;
5869
6138
  }
5870
6139
  getChunksByFile(filePath) {
6140
+ this.throwIfClosed();
5871
6141
  return this.inner.getChunksByFile(filePath);
5872
6142
  }
5873
6143
  getChunksByName(name) {
6144
+ this.throwIfClosed();
5874
6145
  return this.inner.getChunksByName(name);
5875
6146
  }
5876
6147
  getChunksByNameCi(name) {
6148
+ this.throwIfClosed();
5877
6149
  return this.inner.getChunksByNameCi(name);
5878
6150
  }
5879
6151
  deleteChunksByFile(filePath) {
6152
+ this.throwIfClosed();
5880
6153
  return this.inner.deleteChunksByFile(filePath);
5881
6154
  }
6155
+ deleteChunksByIds(chunkIds) {
6156
+ this.throwIfClosed();
6157
+ if (chunkIds.length === 0) return 0;
6158
+ return this.inner.deleteChunksByIds(chunkIds);
6159
+ }
5882
6160
  addChunksToBranch(branch, chunkIds) {
6161
+ this.throwIfClosed();
5883
6162
  this.inner.addChunksToBranch(branch, chunkIds);
5884
6163
  }
5885
6164
  addChunksToBranchBatch(branch, chunkIds) {
6165
+ this.throwIfClosed();
5886
6166
  if (chunkIds.length === 0) return;
5887
6167
  this.inner.addChunksToBranchBatch(branch, chunkIds);
5888
6168
  }
5889
6169
  clearBranch(branch) {
6170
+ this.throwIfClosed();
5890
6171
  return this.inner.clearBranch(branch);
5891
6172
  }
6173
+ deleteBranchChunksByChunkIds(chunkIds) {
6174
+ this.throwIfClosed();
6175
+ if (chunkIds.length === 0) return 0;
6176
+ return this.inner.deleteBranchChunksByChunkIds(chunkIds);
6177
+ }
6178
+ deleteBranchChunksForBranch(branch, chunkIds) {
6179
+ this.throwIfClosed();
6180
+ if (chunkIds.length === 0) return 0;
6181
+ return this.inner.deleteBranchChunksForBranch(branch, chunkIds);
6182
+ }
5892
6183
  getBranchChunkIds(branch) {
6184
+ this.throwIfClosed();
5893
6185
  return this.inner.getBranchChunkIds(branch);
5894
6186
  }
5895
6187
  getBranchDelta(branch, baseBranch) {
6188
+ this.throwIfClosed();
5896
6189
  return this.inner.getBranchDelta(branch, baseBranch);
5897
6190
  }
6191
+ getReferencedChunkIds(chunkIds) {
6192
+ this.throwIfClosed();
6193
+ if (chunkIds.length === 0) return [];
6194
+ return this.inner.getReferencedChunkIds(chunkIds);
6195
+ }
5898
6196
  chunkExistsOnBranch(branch, chunkId) {
6197
+ this.throwIfClosed();
5899
6198
  return this.inner.chunkExistsOnBranch(branch, chunkId);
5900
6199
  }
5901
6200
  getAllBranches() {
6201
+ this.throwIfClosed();
5902
6202
  return this.inner.getAllBranches();
5903
6203
  }
5904
6204
  getMetadata(key) {
6205
+ this.throwIfClosed();
5905
6206
  return this.inner.getMetadata(key) ?? null;
5906
6207
  }
5907
6208
  setMetadata(key, value) {
6209
+ this.throwIfClosed();
5908
6210
  this.inner.setMetadata(key, value);
5909
6211
  }
5910
6212
  deleteMetadata(key) {
6213
+ this.throwIfClosed();
5911
6214
  return this.inner.deleteMetadata(key);
5912
6215
  }
6216
+ clearAllIndexedData() {
6217
+ this.throwIfClosed();
6218
+ this.inner.clearAllIndexedData();
6219
+ }
6220
+ clearCallEdgeTargetsForSymbols(symbolIds) {
6221
+ this.throwIfClosed();
6222
+ if (symbolIds.length === 0) return 0;
6223
+ return this.inner.clearCallEdgeTargetsForSymbols(symbolIds);
6224
+ }
5913
6225
  gcOrphanEmbeddings() {
6226
+ this.throwIfClosed();
5914
6227
  return this.inner.gcOrphanEmbeddings();
5915
6228
  }
5916
6229
  gcOrphanChunks() {
6230
+ this.throwIfClosed();
5917
6231
  return this.inner.gcOrphanChunks();
5918
6232
  }
5919
6233
  getStats() {
6234
+ this.throwIfClosed();
5920
6235
  return this.inner.getStats();
5921
6236
  }
5922
6237
  // ── Symbol methods ──────────────────────────────────────────────
5923
6238
  upsertSymbol(symbol) {
6239
+ this.throwIfClosed();
5924
6240
  this.inner.upsertSymbol(symbol);
5925
6241
  }
5926
6242
  upsertSymbolsBatch(symbols) {
6243
+ this.throwIfClosed();
5927
6244
  if (symbols.length === 0) return;
5928
6245
  this.inner.upsertSymbolsBatch(symbols);
5929
6246
  }
5930
6247
  getSymbolsByFile(filePath) {
6248
+ this.throwIfClosed();
5931
6249
  return this.inner.getSymbolsByFile(filePath);
5932
6250
  }
5933
6251
  getSymbolByName(name, filePath) {
6252
+ this.throwIfClosed();
5934
6253
  return this.inner.getSymbolByName(name, filePath) ?? null;
5935
6254
  }
5936
6255
  getSymbolsByName(name) {
6256
+ this.throwIfClosed();
5937
6257
  return this.inner.getSymbolsByName(name);
5938
6258
  }
5939
6259
  getSymbolsByNameCi(name) {
6260
+ this.throwIfClosed();
5940
6261
  return this.inner.getSymbolsByNameCi(name);
5941
6262
  }
5942
6263
  deleteSymbolsByFile(filePath) {
6264
+ this.throwIfClosed();
5943
6265
  return this.inner.deleteSymbolsByFile(filePath);
5944
6266
  }
5945
6267
  // ── Call Edge methods ────────────────────────────────────────────
5946
6268
  upsertCallEdge(edge) {
6269
+ this.throwIfClosed();
5947
6270
  this.inner.upsertCallEdge(edge);
5948
6271
  }
5949
6272
  upsertCallEdgesBatch(edges) {
6273
+ this.throwIfClosed();
5950
6274
  if (edges.length === 0) return;
5951
6275
  this.inner.upsertCallEdgesBatch(edges);
5952
6276
  }
5953
6277
  getCallers(targetName, branch) {
6278
+ this.throwIfClosed();
5954
6279
  return this.inner.getCallers(targetName, branch);
5955
6280
  }
5956
6281
  getCallersWithContext(targetName, branch) {
6282
+ this.throwIfClosed();
5957
6283
  return this.inner.getCallersWithContext(targetName, branch);
5958
6284
  }
5959
6285
  getCallees(symbolId, branch) {
6286
+ this.throwIfClosed();
5960
6287
  return this.inner.getCallees(symbolId, branch);
5961
6288
  }
5962
6289
  deleteCallEdgesByFile(filePath) {
6290
+ this.throwIfClosed();
5963
6291
  return this.inner.deleteCallEdgesByFile(filePath);
5964
6292
  }
5965
6293
  resolveCallEdge(edgeId, toSymbolId) {
6294
+ this.throwIfClosed();
5966
6295
  this.inner.resolveCallEdge(edgeId, toSymbolId);
5967
6296
  }
5968
6297
  // ── Branch Symbol methods ────────────────────────────────────────
5969
6298
  addSymbolsToBranch(branch, symbolIds) {
6299
+ this.throwIfClosed();
5970
6300
  this.inner.addSymbolsToBranch(branch, symbolIds);
5971
6301
  }
5972
6302
  addSymbolsToBranchBatch(branch, symbolIds) {
6303
+ this.throwIfClosed();
5973
6304
  if (symbolIds.length === 0) return;
5974
6305
  this.inner.addSymbolsToBranchBatch(branch, symbolIds);
5975
6306
  }
5976
6307
  getBranchSymbolIds(branch) {
6308
+ this.throwIfClosed();
5977
6309
  return this.inner.getBranchSymbolIds(branch);
5978
6310
  }
5979
6311
  clearBranchSymbols(branch) {
6312
+ this.throwIfClosed();
5980
6313
  return this.inner.clearBranchSymbols(branch);
5981
6314
  }
6315
+ getReferencedSymbolIds(symbolIds) {
6316
+ this.throwIfClosed();
6317
+ if (symbolIds.length === 0) return [];
6318
+ return this.inner.getReferencedSymbolIds(symbolIds);
6319
+ }
6320
+ deleteBranchSymbolsBySymbolIds(symbolIds) {
6321
+ this.throwIfClosed();
6322
+ if (symbolIds.length === 0) return 0;
6323
+ return this.inner.deleteBranchSymbolsBySymbolIds(symbolIds);
6324
+ }
6325
+ deleteBranchSymbolsForBranch(branch, symbolIds) {
6326
+ this.throwIfClosed();
6327
+ if (symbolIds.length === 0) return 0;
6328
+ return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6329
+ }
5982
6330
  // ── GC methods for symbols/edges ─────────────────────────────────
5983
6331
  gcOrphanSymbols() {
6332
+ this.throwIfClosed();
5984
6333
  return this.inner.gcOrphanSymbols();
5985
6334
  }
5986
6335
  gcOrphanCallEdges() {
6336
+ this.throwIfClosed();
5987
6337
  return this.inner.gcOrphanCallEdges();
5988
6338
  }
5989
6339
  };
5990
6340
 
5991
6341
  // src/indexer/index.ts
5992
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
6342
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6343
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5993
6344
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5994
6345
  "function_declaration",
5995
6346
  "function",
@@ -6011,7 +6362,11 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6011
6362
  "enum_item",
6012
6363
  "trait_item",
6013
6364
  "mod_item",
6014
- "trait_declaration"
6365
+ "trait_declaration",
6366
+ "trigger_declaration",
6367
+ "test_declaration",
6368
+ "struct_declaration",
6369
+ "union_declaration"
6015
6370
  ]);
6016
6371
  function float32ArrayToBuffer(arr) {
6017
6372
  const float32 = new Float32Array(arr);
@@ -6036,12 +6391,192 @@ function isRateLimitError(error) {
6036
6391
  const message = getErrorMessage(error);
6037
6392
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
6038
6393
  }
6394
+ function getSafeEmbeddingChunkTokenLimit(provider) {
6395
+ const providerMaxTokens = provider.modelInfo.maxTokens;
6396
+ const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
6397
+ return Math.min(2e3, maxChunkTokens);
6398
+ }
6399
+ function getDynamicBatchOptions(provider) {
6400
+ if (provider.provider === "ollama") {
6401
+ return {
6402
+ maxBatchTokens: provider.modelInfo.maxTokens,
6403
+ maxBatchItems: 1
6404
+ };
6405
+ }
6406
+ return {};
6407
+ }
6408
+ function isSqliteCorruptionError(error) {
6409
+ const message = getErrorMessage(error).toLowerCase();
6410
+ return message.includes("database disk image is malformed") || message.includes("file is not a database") || message.includes("database schema is corrupt") || message.includes("sqlite_corrupt");
6411
+ }
6412
+ var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
6039
6413
  var INDEX_METADATA_VERSION = "1";
6414
+ var EMBEDDING_STRATEGY_VERSION = "2";
6040
6415
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
6416
+ var RANK_HYBRID_CACHE_LIMIT = 256;
6417
+ function createPendingChunkStorageText(texts) {
6418
+ const primaryText = texts[0]?.text ?? "";
6419
+ if (texts.length <= 1) {
6420
+ return primaryText;
6421
+ }
6422
+ return `${primaryText}
6423
+
6424
+ ... [split into ${texts.length} parts for embedding]`;
6425
+ }
6426
+ function normalizePendingChunk(rawChunk, maxChunkTokens) {
6427
+ if (!rawChunk || typeof rawChunk !== "object") {
6428
+ return null;
6429
+ }
6430
+ const chunk = rawChunk;
6431
+ if (typeof chunk.id !== "string" || typeof chunk.contentHash !== "string" || !chunk.metadata || typeof chunk.metadata !== "object") {
6432
+ return null;
6433
+ }
6434
+ const texts = Array.isArray(chunk.texts) ? chunk.texts.map((entry) => {
6435
+ if (!entry || typeof entry.text !== "string") {
6436
+ return null;
6437
+ }
6438
+ return {
6439
+ text: entry.text,
6440
+ tokenCount: typeof entry.tokenCount === "number" && Number.isFinite(entry.tokenCount) ? entry.tokenCount : estimateTokens(entry.text)
6441
+ };
6442
+ }).filter((entry) => entry !== null) : [];
6443
+ if (texts.length === 0 && typeof chunk.text === "string") {
6444
+ if (typeof chunk.content === "string" && chunk.content.length > 0 && chunk.metadata && typeof chunk.metadata === "object") {
6445
+ const metadata = chunk.metadata;
6446
+ const rebuiltChunk = {
6447
+ content: chunk.content,
6448
+ startLine: typeof metadata.startLine === "number" ? metadata.startLine : 1,
6449
+ endLine: typeof metadata.endLine === "number" ? metadata.endLine : 1,
6450
+ chunkType: typeof metadata.chunkType === "string" ? metadata.chunkType : "other",
6451
+ name: typeof metadata.name === "string" ? metadata.name : void 0,
6452
+ language: typeof metadata.language === "string" ? metadata.language : "text"
6453
+ };
6454
+ const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
6455
+ texts.push(
6456
+ ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text) => ({
6457
+ text,
6458
+ tokenCount: estimateTokens(text)
6459
+ }))
6460
+ );
6461
+ } else {
6462
+ texts.push({
6463
+ text: chunk.text,
6464
+ tokenCount: estimateTokens(chunk.text)
6465
+ });
6466
+ }
6467
+ }
6468
+ if (texts.length === 0) {
6469
+ return null;
6470
+ }
6471
+ return {
6472
+ id: chunk.id,
6473
+ texts,
6474
+ storageText: typeof chunk.storageText === "string" ? chunk.storageText : createPendingChunkStorageText(texts),
6475
+ content: typeof chunk.content === "string" ? chunk.content : "",
6476
+ contentHash: chunk.contentHash,
6477
+ metadata: chunk.metadata
6478
+ };
6479
+ }
6480
+ function getPendingChunkFilePath(rawChunk) {
6481
+ if (!rawChunk || typeof rawChunk !== "object") {
6482
+ return null;
6483
+ }
6484
+ const chunk = rawChunk;
6485
+ if (!chunk.metadata || typeof chunk.metadata !== "object") {
6486
+ return null;
6487
+ }
6488
+ const metadata = chunk.metadata;
6489
+ return typeof metadata.filePath === "string" ? metadata.filePath : null;
6490
+ }
6491
+ function normalizeFailedBatch(batch, maxChunkTokens) {
6492
+ const chunks = batch.chunks.map((chunk) => normalizePendingChunk(chunk, maxChunkTokens)).filter((chunk) => chunk !== null);
6493
+ if (chunks.length === 0) {
6494
+ return null;
6495
+ }
6496
+ return {
6497
+ chunks,
6498
+ error: batch.error,
6499
+ attemptCount: batch.attemptCount,
6500
+ lastAttempt: batch.lastAttempt
6501
+ };
6502
+ }
6503
+ function createPendingEmbeddingRequests(chunks) {
6504
+ return chunks.flatMap(
6505
+ (chunk) => chunk.texts.map((textPart, partIndex) => ({
6506
+ chunk,
6507
+ partIndex,
6508
+ text: textPart.text,
6509
+ tokenCount: textPart.tokenCount
6510
+ }))
6511
+ );
6512
+ }
6513
+ function createPendingEmbeddingRequestBatches(chunks, options = {}) {
6514
+ return createDynamicBatches(createPendingEmbeddingRequests(chunks), options);
6515
+ }
6516
+ function getUniquePendingChunksFromRequests(requests) {
6517
+ const uniqueChunks = /* @__PURE__ */ new Map();
6518
+ for (const request of requests) {
6519
+ uniqueChunks.set(request.chunk.id, request.chunk);
6520
+ }
6521
+ return Array.from(uniqueChunks.values());
6522
+ }
6523
+ function coalesceFailedBatches(batches) {
6524
+ const grouped = /* @__PURE__ */ new Map();
6525
+ for (const batch of batches) {
6526
+ const key = `${batch.attemptCount}:${batch.lastAttempt}:${batch.error}`;
6527
+ const existing = grouped.get(key);
6528
+ if (!existing) {
6529
+ grouped.set(key, {
6530
+ ...batch,
6531
+ chunks: [...batch.chunks]
6532
+ });
6533
+ continue;
6534
+ }
6535
+ existing.chunks.push(...batch.chunks);
6536
+ }
6537
+ return Array.from(grouped.values());
6538
+ }
6539
+ function poolEmbeddingVectors(vectors, weights) {
6540
+ const firstVector = vectors[0];
6541
+ if (!firstVector) {
6542
+ return [];
6543
+ }
6544
+ const pooled = new Array(firstVector.length).fill(0);
6545
+ let totalWeight = 0;
6546
+ for (let index = 0; index < vectors.length; index++) {
6547
+ const vector = vectors[index];
6548
+ const weight = Math.max(1, weights[index] ?? 1);
6549
+ totalWeight += weight;
6550
+ for (let dimension = 0; dimension < vector.length; dimension++) {
6551
+ pooled[dimension] += vector[dimension] * weight;
6552
+ }
6553
+ }
6554
+ if (totalWeight === 0) {
6555
+ return firstVector;
6556
+ }
6557
+ return pooled.map((value) => value / totalWeight);
6558
+ }
6559
+ function hasAllEmbeddingParts(parts, expectedPartCount) {
6560
+ if (parts.length !== expectedPartCount) {
6561
+ return false;
6562
+ }
6563
+ for (let index = 0; index < expectedPartCount; index++) {
6564
+ if (parts[index] === void 0) {
6565
+ return false;
6566
+ }
6567
+ }
6568
+ return true;
6569
+ }
6570
+ 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}`);
6574
+ }
6041
6575
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6042
6576
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
6043
6577
  var rankingPathTokenCache = /* @__PURE__ */ new Map();
6044
6578
  var rankingTextTokenCache = /* @__PURE__ */ new Map();
6579
+ var rankHybridResultsCache = /* @__PURE__ */ new WeakMap();
6045
6580
  var STOPWORDS = /* @__PURE__ */ new Set([
6046
6581
  "the",
6047
6582
  "and",
@@ -6257,7 +6792,16 @@ function classifyExternalRerankBand(candidate, preferSourcePaths, docIntent) {
6257
6792
  function classifyQueryIntent(tokens) {
6258
6793
  const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
6259
6794
  const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
6260
- return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
6795
+ if (sourceIntentHits === 0 && docTestIntentHits === 0) {
6796
+ return "neutral";
6797
+ }
6798
+ if (sourceIntentHits > docTestIntentHits) {
6799
+ return "source";
6800
+ }
6801
+ if (docTestIntentHits > sourceIntentHits) {
6802
+ return "doc_test";
6803
+ }
6804
+ return "neutral";
6261
6805
  }
6262
6806
  function classifyQueryIntentRaw(query) {
6263
6807
  const lowerQuery = query.toLowerCase();
@@ -6279,7 +6823,7 @@ function classifyQueryIntentRaw(query) {
6279
6823
  if (docTestRawHits > sourceRawHits) {
6280
6824
  return "doc_test";
6281
6825
  }
6282
- if (sourceRawHits > docTestRawHits) {
6826
+ if (sourceRawHits > docTestRawHits && sourceRawHits > 0) {
6283
6827
  return "source";
6284
6828
  }
6285
6829
  const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
@@ -6546,9 +7090,8 @@ function rerankResults(query, candidates, rerankTopN, options) {
6546
7090
  return candidates;
6547
7091
  }
6548
7092
  const queryTokenList = Array.from(queryTokens);
6549
- const intent = classifyQueryIntentRaw(query);
6550
7093
  const docIntent = classifyDocIntent(queryTokenList);
6551
- const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
7094
+ const preferSourcePaths = options?.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
6552
7095
  const identifierHints = extractIdentifierHints(query);
6553
7096
  const head = candidates.slice(0, topN).map((candidate, idx) => {
6554
7097
  const pathTokens = splitPathTokens(candidate.metadata.filePath);
@@ -6698,13 +7241,38 @@ function buildDiversityKey(metadata) {
6698
7241
  return normalizedPath;
6699
7242
  }
6700
7243
  function rankHybridResults(query, semanticResults, keywordResults, options) {
7244
+ const prioritizeSourcePaths = options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
7245
+ const cacheKey = `${query}${options.fusionStrategy}|${options.rrfK}|${options.hybridWeight}|${options.rerankTopN}|${options.limit}|${prioritizeSourcePaths ? 1 : 0}`;
7246
+ let byKeyword = rankHybridResultsCache.get(semanticResults);
7247
+ if (!byKeyword) {
7248
+ byKeyword = /* @__PURE__ */ new WeakMap();
7249
+ rankHybridResultsCache.set(semanticResults, byKeyword);
7250
+ }
7251
+ let bucket = byKeyword.get(keywordResults);
7252
+ if (!bucket) {
7253
+ bucket = /* @__PURE__ */ new Map();
7254
+ byKeyword.set(keywordResults, bucket);
7255
+ } else {
7256
+ const cached = bucket.get(cacheKey);
7257
+ if (cached) {
7258
+ return cached;
7259
+ }
7260
+ }
6701
7261
  const overfetchLimit = Math.max(options.limit * 4, options.limit);
6702
7262
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
6703
7263
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
6704
7264
  const rerankPool = fused.slice(0, rerankPoolLimit);
6705
- return rerankResults(query, rerankPool, options.rerankTopN, {
6706
- prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
7265
+ const ranked = rerankResults(query, rerankPool, options.rerankTopN, {
7266
+ prioritizeSourcePaths
6707
7267
  });
7268
+ if (bucket.size >= RANK_HYBRID_CACHE_LIMIT) {
7269
+ const oldest = bucket.keys().next().value;
7270
+ if (oldest !== void 0) {
7271
+ bucket.delete(oldest);
7272
+ }
7273
+ }
7274
+ bucket.set(cacheKey, ranked);
7275
+ return ranked;
6708
7276
  }
6709
7277
  function rankSemanticOnlyResults(query, semanticResults, options) {
6710
7278
  const overfetchLimit = Math.max(options.limit * 4, options.limit);
@@ -7001,6 +7569,21 @@ function mergeTieredResults(symbolLane, hybridLane, limit) {
7001
7569
  }
7002
7570
  return out;
7003
7571
  }
7572
+ function matchesSearchFilters(candidate, options, minScore) {
7573
+ if (candidate.score < minScore) return false;
7574
+ if (options?.fileType) {
7575
+ const ext = candidate.metadata.filePath.split(".").pop()?.toLowerCase();
7576
+ if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
7577
+ }
7578
+ if (options?.directory) {
7579
+ const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
7580
+ if (!candidate.metadata.filePath.includes(`/${normalizedDir}/`) && !candidate.metadata.filePath.includes(`${normalizedDir}/`)) return false;
7581
+ }
7582
+ if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
7583
+ return false;
7584
+ }
7585
+ return true;
7586
+ }
7004
7587
  function unionCandidates(semanticCandidates, keywordCandidates) {
7005
7588
  const byId = /* @__PURE__ */ new Map();
7006
7589
  for (const candidate of semanticCandidates) {
@@ -7040,22 +7623,18 @@ var Indexer = class {
7040
7623
  this.projectRoot = projectRoot;
7041
7624
  this.config = config;
7042
7625
  this.indexPath = this.getIndexPath();
7043
- this.fileHashCachePath = path7.join(this.indexPath, "file-hashes.json");
7044
- this.failedBatchesPath = path7.join(this.indexPath, "failed-batches.json");
7045
- this.indexingLockPath = path7.join(this.indexPath, "indexing.lock");
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");
7046
7629
  this.logger = initializeLogger(config.debug);
7047
7630
  }
7048
7631
  getIndexPath() {
7049
- if (this.config.scope === "global") {
7050
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
7051
- return path7.join(homeDir, ".opencode", "global-index");
7052
- }
7053
- return path7.join(this.projectRoot, ".opencode", "index");
7632
+ return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7054
7633
  }
7055
7634
  loadFileHashCache() {
7056
7635
  try {
7057
- if ((0, import_fs5.existsSync)(this.fileHashCachePath)) {
7058
- const data = (0, import_fs5.readFileSync)(this.fileHashCachePath, "utf-8");
7636
+ if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
7637
+ const data = (0, import_fs6.readFileSync)(this.fileHashCachePath, "utf-8");
7059
7638
  const parsed = JSON.parse(data);
7060
7639
  this.fileHashCache = new Map(Object.entries(parsed));
7061
7640
  }
@@ -7072,91 +7651,450 @@ var Indexer = class {
7072
7651
  }
7073
7652
  atomicWriteSync(targetPath, data) {
7074
7653
  const tempPath = `${targetPath}.tmp`;
7075
- (0, import_fs5.writeFileSync)(tempPath, data);
7076
- (0, import_fs5.renameSync)(tempPath, targetPath);
7654
+ (0, import_fs6.writeFileSync)(tempPath, data);
7655
+ (0, import_fs6.renameSync)(tempPath, targetPath);
7077
7656
  }
7078
- checkForInterruptedIndexing() {
7079
- return (0, import_fs5.existsSync)(this.indexingLockPath);
7657
+ getScopedRoots() {
7658
+ const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7659
+ for (const kbRoot of this.config.knowledgeBases) {
7660
+ roots.add(path8.resolve(this.projectRoot, kbRoot));
7661
+ }
7662
+ return Array.from(roots);
7080
7663
  }
7081
- acquireIndexingLock() {
7082
- const lockData = {
7083
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7084
- pid: process.pid
7085
- };
7086
- (0, import_fs5.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
7664
+ getBranchCatalogKey() {
7665
+ const branchName = this.currentBranch || "default";
7666
+ if (this.config.scope !== "global") {
7667
+ return branchName;
7668
+ }
7669
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7670
+ return `${projectHash}:${branchName}`;
7087
7671
  }
7088
- releaseIndexingLock() {
7089
- if ((0, import_fs5.existsSync)(this.indexingLockPath)) {
7090
- (0, import_fs5.unlinkSync)(this.indexingLockPath);
7672
+ getLegacyBranchCatalogKey() {
7673
+ return this.currentBranch || "default";
7674
+ }
7675
+ getLegacyMigrationMetadataKey() {
7676
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7677
+ return `index.globalBranchMigration.${projectHash}`;
7678
+ }
7679
+ getProjectEmbeddingStrategyMetadataKey() {
7680
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7681
+ return `index.embeddingStrategyVersion.${projectHash}`;
7682
+ }
7683
+ getProjectForceReembedMetadataKey() {
7684
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7685
+ return `index.forceReembed.${projectHash}`;
7686
+ }
7687
+ hasProjectForceReembedPending() {
7688
+ return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7689
+ }
7690
+ hasScopedIndexedData() {
7691
+ if (!this.store || this.config.scope !== "global") {
7692
+ return false;
7693
+ }
7694
+ if (this.hasProjectForceReembedPending()) {
7695
+ return false;
7696
+ }
7697
+ const roots = this.getScopedRoots();
7698
+ if (Array.from(this.fileHashCache.keys()).some((filePath) => this.isFileInCurrentScope(filePath, roots))) {
7699
+ return true;
7700
+ }
7701
+ if (this.loadSerializedFailedBatches().some(
7702
+ (batch) => batch.chunks.some((chunk) => {
7703
+ const filePath = getPendingChunkFilePath(chunk);
7704
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7705
+ })
7706
+ )) {
7707
+ return true;
7708
+ }
7709
+ if (!this.database) {
7710
+ return false;
7091
7711
  }
7712
+ if (this.getBranchCatalogKeys().some((branchKey) => {
7713
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7714
+ if (branchChunkIds.length > 0) {
7715
+ return true;
7716
+ }
7717
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7718
+ })) {
7719
+ return true;
7720
+ }
7721
+ const hasAnyBranchRows = this.database.getAllBranches().some((branchKey) => {
7722
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7723
+ if (branchChunkIds.length > 0) {
7724
+ return true;
7725
+ }
7726
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7727
+ });
7728
+ if (hasAnyBranchRows) {
7729
+ return false;
7730
+ }
7731
+ return this.store.getAllMetadata().some(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7092
7732
  }
7093
- async recoverFromInterruptedIndexing() {
7094
- this.logger.warn("Detected interrupted indexing session, recovering...");
7095
- if ((0, import_fs5.existsSync)(this.fileHashCachePath)) {
7096
- (0, import_fs5.unlinkSync)(this.fileHashCachePath);
7733
+ loadStoredEmbeddingStrategyVersion() {
7734
+ if (!this.database) {
7735
+ return null;
7097
7736
  }
7098
- await this.healthCheck();
7099
- this.releaseIndexingLock();
7100
- this.logger.info("Recovery complete, next index will re-process all files");
7737
+ if (this.hasProjectForceReembedPending()) {
7738
+ return null;
7739
+ }
7740
+ if (this.config.scope !== "global") {
7741
+ return this.database.getMetadata("index.embeddingStrategyVersion") ?? "1";
7742
+ }
7743
+ const projectVersion = this.database.getMetadata(this.getProjectEmbeddingStrategyMetadataKey());
7744
+ if (projectVersion) {
7745
+ return projectVersion;
7746
+ }
7747
+ const legacySharedVersion = this.database.getMetadata("index.embeddingStrategyVersion");
7748
+ if (legacySharedVersion && this.hasScopedIndexedData()) {
7749
+ return legacySharedVersion;
7750
+ }
7751
+ return null;
7101
7752
  }
7102
- loadFailedBatches() {
7103
- try {
7104
- if ((0, import_fs5.existsSync)(this.failedBatchesPath)) {
7105
- const data = (0, import_fs5.readFileSync)(this.failedBatchesPath, "utf-8");
7106
- return JSON.parse(data);
7753
+ getBranchCatalogKeys() {
7754
+ const primary = this.getBranchCatalogKey();
7755
+ if (this.config.scope !== "global") {
7756
+ return [primary];
7757
+ }
7758
+ if (this.database?.getMetadata(this.getLegacyMigrationMetadataKey()) === "done") {
7759
+ return [primary];
7760
+ }
7761
+ const legacy = this.getLegacyBranchCatalogKey();
7762
+ return primary === legacy ? [primary] : [primary, legacy];
7763
+ }
7764
+ getBranchCatalogCleanupKeys() {
7765
+ const primary = this.getBranchCatalogKey();
7766
+ if (this.config.scope !== "global") {
7767
+ return [primary];
7768
+ }
7769
+ const legacy = this.getLegacyBranchCatalogKey();
7770
+ return primary === legacy ? [primary] : [primary, legacy];
7771
+ }
7772
+ getProjectLocalScopedOwnershipIds(roots) {
7773
+ const chunkIds = /* @__PURE__ */ new Set();
7774
+ const symbolIds = /* @__PURE__ */ new Set();
7775
+ if (!this.database) {
7776
+ return { chunkIds, symbolIds };
7777
+ }
7778
+ const projectRootPath = path8.resolve(this.projectRoot);
7779
+ const projectLocalFilePaths = /* @__PURE__ */ new Set([
7780
+ ...Array.from(this.fileHashCache.keys()).filter(
7781
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7782
+ ),
7783
+ ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
7784
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7785
+ )
7786
+ ]);
7787
+ for (const filePath of projectLocalFilePaths) {
7788
+ for (const chunk of this.database.getChunksByFile(filePath)) {
7789
+ chunkIds.add(chunk.chunkId);
7790
+ }
7791
+ for (const symbol of this.database.getSymbolsByFile(filePath)) {
7792
+ symbolIds.add(symbol.id);
7107
7793
  }
7108
- } catch {
7109
- return [];
7110
7794
  }
7111
- return [];
7795
+ return { chunkIds, symbolIds };
7112
7796
  }
7113
- saveFailedBatches(batches) {
7114
- if (batches.length === 0) {
7115
- if ((0, import_fs5.existsSync)(this.failedBatchesPath)) {
7116
- import_fs5.promises.unlink(this.failedBatchesPath).catch(() => {
7117
- });
7797
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
7798
+ if (this.config.scope !== "global") {
7799
+ return this.getBranchCatalogCleanupKeys();
7800
+ }
7801
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7802
+ const keys = /* @__PURE__ */ new Set();
7803
+ const projectChunkIdSet = new Set(projectChunkIds);
7804
+ const projectSymbolIdSet = new Set(projectSymbolIds);
7805
+ for (const branchKey of this.database?.getAllBranches() ?? []) {
7806
+ if (branchKey.startsWith(`${projectHash}:`)) {
7807
+ keys.add(branchKey);
7808
+ continue;
7809
+ }
7810
+ if (branchKey.includes(":")) {
7811
+ continue;
7812
+ }
7813
+ const referencesProjectChunks = this.database?.getBranchChunkIds(branchKey).some((chunkId) => projectChunkIdSet.has(chunkId)) ?? false;
7814
+ const referencesProjectSymbols = this.database?.getBranchSymbolIds(branchKey).some((symbolId) => projectSymbolIdSet.has(symbolId)) ?? false;
7815
+ if (referencesProjectChunks || referencesProjectSymbols) {
7816
+ keys.add(branchKey);
7118
7817
  }
7119
- return;
7120
7818
  }
7121
- (0, import_fs5.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7819
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
7820
+ keys.add(branchKey);
7821
+ }
7822
+ return Array.from(keys);
7122
7823
  }
7123
- addFailedBatch(batch, error) {
7124
- const existing = this.loadFailedBatches();
7125
- existing.push({
7126
- chunks: batch,
7127
- error,
7128
- attemptCount: 1,
7129
- lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
7130
- });
7131
- this.saveFailedBatches(existing);
7824
+ isFileInCurrentScope(filePath, roots) {
7825
+ return roots.some((root) => isPathWithinRoot(filePath, root));
7132
7826
  }
7133
- getProviderRateLimits(provider) {
7134
- switch (provider) {
7135
- case "github-copilot":
7136
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
7137
- case "openai":
7138
- return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
7139
- case "google":
7140
- return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
7141
- case "ollama":
7142
- return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
7143
- case "custom": {
7144
- const customConfig = this.config.customProvider;
7145
- return {
7146
- concurrency: customConfig?.concurrency ?? 3,
7147
- intervalMs: customConfig?.requestIntervalMs ?? 1e3,
7148
- minRetryMs: 1e3,
7149
- maxRetryMs: 3e4
7150
- };
7827
+ clearScopedFileHashCache(roots) {
7828
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7829
+ if (this.isFileInCurrentScope(filePath, roots)) {
7830
+ this.fileHashCache.delete(filePath);
7151
7831
  }
7152
- default:
7153
- return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
7154
7832
  }
7833
+ this.saveFileHashCache();
7155
7834
  }
7156
- async rerankCandidatesWithApi(query, candidates, options) {
7157
- const reranker = this.config.reranker;
7158
- if (!reranker || !reranker.enabled || candidates.length <= 1) {
7159
- return candidates;
7835
+ replaceScopedFileHashCache(currentFileHashes, roots) {
7836
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7837
+ if (this.isFileInCurrentScope(filePath, roots)) {
7838
+ this.fileHashCache.delete(filePath);
7839
+ }
7840
+ }
7841
+ for (const [filePath, hash] of currentFileHashes) {
7842
+ this.fileHashCache.set(filePath, hash);
7843
+ }
7844
+ this.saveFileHashCache();
7845
+ }
7846
+ partitionFailedBatches(roots, maxChunkTokens) {
7847
+ const scoped = [];
7848
+ const retained = [];
7849
+ for (const batch of this.loadSerializedFailedBatches()) {
7850
+ const scopedChunks = batch.chunks.filter((chunk) => {
7851
+ const filePath = getPendingChunkFilePath(chunk);
7852
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7853
+ });
7854
+ const retainedChunks = batch.chunks.filter((chunk) => {
7855
+ const filePath = getPendingChunkFilePath(chunk);
7856
+ return filePath === null || !this.isFileInCurrentScope(filePath, roots);
7857
+ });
7858
+ if (scopedChunks.length > 0) {
7859
+ const normalizedBatch = normalizeFailedBatch({ ...batch, chunks: scopedChunks }, maxChunkTokens);
7860
+ if (normalizedBatch) {
7861
+ scoped.push(normalizedBatch);
7862
+ }
7863
+ }
7864
+ if (retainedChunks.length > 0) {
7865
+ retained.push({ ...batch, chunks: retainedChunks });
7866
+ }
7867
+ }
7868
+ return { scoped, retained };
7869
+ }
7870
+ clearScopedFailedBatches(roots) {
7871
+ const { retained: retainedBatches } = this.partitionFailedBatches(roots);
7872
+ this.saveFailedBatches(retainedBatches);
7873
+ }
7874
+ hasForeignScopedFileHashData(roots) {
7875
+ return Array.from(this.fileHashCache.keys()).some((filePath) => !this.isFileInCurrentScope(filePath, roots));
7876
+ }
7877
+ hasForeignScopedFailedBatches(roots) {
7878
+ const { retained } = this.partitionFailedBatches(roots);
7879
+ return retained.length > 0;
7880
+ }
7881
+ hasForeignScopedBranchData() {
7882
+ if (!this.database || this.config.scope !== "global") {
7883
+ return false;
7884
+ }
7885
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7886
+ const roots = this.getScopedRoots();
7887
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7888
+ return this.database.getAllBranches().some(
7889
+ (branchKey) => {
7890
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7891
+ const branchSymbolIds = this.database.getBranchSymbolIds(branchKey);
7892
+ const hasBranchData = branchChunkIds.length > 0 || branchSymbolIds.length > 0;
7893
+ if (!hasBranchData) {
7894
+ return false;
7895
+ }
7896
+ if (branchKey.startsWith(`${projectHash}:`)) {
7897
+ return false;
7898
+ }
7899
+ if (!branchKey.includes(":")) {
7900
+ const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
7901
+ const referencesCurrentProjectSymbols = branchSymbolIds.some((symbolId) => projectLocalSymbolIds.has(symbolId));
7902
+ return !(referencesCurrentProjectChunks || referencesCurrentProjectSymbols);
7903
+ }
7904
+ return true;
7905
+ }
7906
+ );
7907
+ }
7908
+ saveScopedFailedBatches(batches, roots) {
7909
+ const { retained } = this.partitionFailedBatches(roots);
7910
+ this.saveFailedBatches([...retained, ...batches]);
7911
+ }
7912
+ clearSharedIndexProjectData(store, invertedIndex, database, roots) {
7913
+ const allMetadata = store.getAllMetadata();
7914
+ const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7915
+ const filePaths = /* @__PURE__ */ new Set([
7916
+ ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7917
+ ...scopedEntries.map(({ metadata }) => metadata.filePath)
7918
+ ]);
7919
+ const projectRootPath = path8.resolve(this.projectRoot);
7920
+ const projectLocalFilePaths = new Set(
7921
+ Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7922
+ );
7923
+ const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
7924
+ for (const filePath of filePaths) {
7925
+ for (const chunk of database.getChunksByFile(filePath)) {
7926
+ removedChunkIds.add(chunk.chunkId);
7927
+ }
7928
+ }
7929
+ const removedChunkIdList = Array.from(removedChunkIds);
7930
+ const projectLocalChunkIds = new Set(
7931
+ scopedEntries.filter(({ metadata }) => isPathWithinRoot(metadata.filePath, projectRootPath)).map(({ key }) => key)
7932
+ );
7933
+ for (const filePath of projectLocalFilePaths) {
7934
+ for (const chunk of database.getChunksByFile(filePath)) {
7935
+ projectLocalChunkIds.add(chunk.chunkId);
7936
+ }
7937
+ }
7938
+ const symbolIds = [];
7939
+ const projectLocalSymbolIds = /* @__PURE__ */ new Set();
7940
+ for (const filePath of filePaths) {
7941
+ for (const symbol of database.getSymbolsByFile(filePath)) {
7942
+ symbolIds.push(symbol.id);
7943
+ if (projectLocalFilePaths.has(filePath)) {
7944
+ projectLocalSymbolIds.add(symbol.id);
7945
+ }
7946
+ }
7947
+ }
7948
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7949
+ database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
7950
+ }
7951
+ const sharedChunkIds = new Set(database.getReferencedChunkIds(removedChunkIdList));
7952
+ const removableChunkIds = removedChunkIdList.filter((chunkId) => !sharedChunkIds.has(chunkId));
7953
+ if (removableChunkIds.length > 0) {
7954
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removableChunkIds);
7955
+ for (const chunkId of removableChunkIds) {
7956
+ invertedIndex.removeChunk(chunkId);
7957
+ }
7958
+ }
7959
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7960
+ database.deleteBranchSymbolsForBranch(branchKey, symbolIds);
7961
+ }
7962
+ const sharedSymbolIds = new Set(database.getReferencedSymbolIds(symbolIds));
7963
+ const removableSymbolIds = symbolIds.filter((symbolId) => !sharedSymbolIds.has(symbolId));
7964
+ database.clearCallEdgeTargetsForSymbols(removableSymbolIds);
7965
+ for (const filePath of filePaths) {
7966
+ const fileChunkIds = database.getChunksByFile(filePath).map((chunk) => chunk.chunkId);
7967
+ const fileSymbols = database.getSymbolsByFile(filePath);
7968
+ if (fileChunkIds.every((chunkId) => !sharedChunkIds.has(chunkId))) {
7969
+ database.deleteChunksByFile(filePath);
7970
+ }
7971
+ if (fileSymbols.every((symbol) => !sharedSymbolIds.has(symbol.id))) {
7972
+ database.deleteCallEdgesByFile(filePath);
7973
+ database.deleteSymbolsByFile(filePath);
7974
+ }
7975
+ }
7976
+ database.gcOrphanCallEdges();
7977
+ database.gcOrphanSymbols();
7978
+ database.gcOrphanEmbeddings();
7979
+ database.gcOrphanChunks();
7980
+ store.save();
7981
+ invertedIndex.save();
7982
+ return {
7983
+ removedChunkIds: removedChunkIdList,
7984
+ hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
7985
+ };
7986
+ }
7987
+ checkForInterruptedIndexing() {
7988
+ return (0, import_fs6.existsSync)(this.indexingLockPath);
7989
+ }
7990
+ acquireIndexingLock() {
7991
+ const lockData = {
7992
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7993
+ pid: process.pid
7994
+ };
7995
+ (0, import_fs6.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
7996
+ }
7997
+ releaseIndexingLock() {
7998
+ if ((0, import_fs6.existsSync)(this.indexingLockPath)) {
7999
+ (0, import_fs6.unlinkSync)(this.indexingLockPath);
8000
+ }
8001
+ }
8002
+ async recoverFromInterruptedIndexing() {
8003
+ this.logger.warn("Detected interrupted indexing session, recovering...");
8004
+ if ((0, import_fs6.existsSync)(this.fileHashCachePath)) {
8005
+ (0, import_fs6.unlinkSync)(this.fileHashCachePath);
8006
+ }
8007
+ await this.healthCheck();
8008
+ this.releaseIndexingLock();
8009
+ this.logger.info("Recovery complete, next index will re-process all files");
8010
+ }
8011
+ loadFailedBatches(maxChunkTokens) {
8012
+ try {
8013
+ return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8014
+ } catch {
8015
+ return [];
8016
+ }
8017
+ }
8018
+ loadSerializedFailedBatches() {
8019
+ if (!(0, import_fs6.existsSync)(this.failedBatchesPath)) {
8020
+ return [];
8021
+ }
8022
+ const data = (0, import_fs6.readFileSync)(this.failedBatchesPath, "utf-8");
8023
+ const parsed = JSON.parse(data);
8024
+ return parsed.map((batch) => {
8025
+ const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
8026
+ if (chunks.length === 0) {
8027
+ return null;
8028
+ }
8029
+ return {
8030
+ chunks,
8031
+ error: typeof batch.error === "string" ? batch.error : "Unknown embedding error",
8032
+ attemptCount: typeof batch.attemptCount === "number" ? batch.attemptCount : 1,
8033
+ lastAttempt: typeof batch.lastAttempt === "string" ? batch.lastAttempt : (/* @__PURE__ */ new Date()).toISOString()
8034
+ };
8035
+ }).filter((batch) => batch !== null);
8036
+ }
8037
+ saveFailedBatches(batches) {
8038
+ if (batches.length === 0) {
8039
+ if ((0, import_fs6.existsSync)(this.failedBatchesPath)) {
8040
+ try {
8041
+ (0, import_fs6.unlinkSync)(this.failedBatchesPath);
8042
+ } catch {
8043
+ }
8044
+ }
8045
+ return;
8046
+ }
8047
+ (0, import_fs6.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8048
+ }
8049
+ collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8050
+ const retryableById = /* @__PURE__ */ new Map();
8051
+ for (const batch of this.loadFailedBatches(maxChunkTokens)) {
8052
+ for (const chunk of batch.chunks) {
8053
+ const filePath = chunk.metadata.filePath;
8054
+ if (!currentFileHashes.has(filePath)) {
8055
+ continue;
8056
+ }
8057
+ if (!unchangedFilePaths.has(filePath)) {
8058
+ continue;
8059
+ }
8060
+ const existing = retryableById.get(chunk.id);
8061
+ if (!existing || batch.attemptCount > existing.attemptCount) {
8062
+ retryableById.set(chunk.id, {
8063
+ chunk,
8064
+ attemptCount: batch.attemptCount
8065
+ });
8066
+ }
8067
+ }
8068
+ }
8069
+ return Array.from(retryableById.values());
8070
+ }
8071
+ getProviderRateLimits(provider) {
8072
+ switch (provider) {
8073
+ case "github-copilot":
8074
+ return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
8075
+ case "openai":
8076
+ return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
8077
+ case "google":
8078
+ return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
8079
+ case "ollama":
8080
+ return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
8081
+ case "custom": {
8082
+ const customConfig = this.config.customProvider;
8083
+ return {
8084
+ concurrency: customConfig?.concurrency ?? 3,
8085
+ intervalMs: customConfig?.requestIntervalMs ?? 1e3,
8086
+ minRetryMs: 1e3,
8087
+ maxRetryMs: 3e4
8088
+ };
8089
+ }
8090
+ default:
8091
+ return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
8092
+ }
8093
+ }
8094
+ async rerankCandidatesWithApi(query, candidates, options) {
8095
+ const reranker = this.config.reranker;
8096
+ if (!reranker || !reranker.enabled || candidates.length <= 1) {
8097
+ return candidates;
7160
8098
  }
7161
8099
  const queryTokens = Array.from(tokenizeTextForRanking(query));
7162
8100
  const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
@@ -7286,7 +8224,7 @@ var Indexer = class {
7286
8224
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
7287
8225
  parts.push(`intent_hint: ${intent}`);
7288
8226
  try {
7289
- const fileContent = await import_fs5.promises.readFile(candidate.metadata.filePath, "utf-8");
8227
+ const fileContent = await import_fs6.promises.readFile(candidate.metadata.filePath, "utf-8");
7290
8228
  const lines = fileContent.split("\n");
7291
8229
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
7292
8230
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -7331,33 +8269,56 @@ var Indexer = class {
7331
8269
  });
7332
8270
  }
7333
8271
  }
7334
- await import_fs5.promises.mkdir(this.indexPath, { recursive: true });
8272
+ await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
7335
8273
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7336
- const storePath = path7.join(this.indexPath, "vectors");
8274
+ const storePath = path8.join(this.indexPath, "vectors");
7337
8275
  this.store = new VectorStore(storePath, dimensions);
7338
- const indexFilePath = path7.join(this.indexPath, "vectors.usearch");
7339
- if ((0, import_fs5.existsSync)(indexFilePath)) {
8276
+ const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8277
+ if ((0, import_fs6.existsSync)(indexFilePath)) {
7340
8278
  this.store.load();
7341
8279
  }
7342
- const invertedIndexPath = path7.join(this.indexPath, "inverted-index.json");
8280
+ const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
7343
8281
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7344
8282
  try {
7345
8283
  this.invertedIndex.load();
7346
8284
  } catch {
7347
- if ((0, import_fs5.existsSync)(invertedIndexPath)) {
7348
- await import_fs5.promises.unlink(invertedIndexPath);
8285
+ if ((0, import_fs6.existsSync)(invertedIndexPath)) {
8286
+ await import_fs6.promises.unlink(invertedIndexPath);
8287
+ }
8288
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
8289
+ }
8290
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8291
+ let dbIsNew = !(0, import_fs6.existsSync)(dbPath);
8292
+ try {
8293
+ this.database = new Database(dbPath);
8294
+ } catch (error) {
8295
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
8296
+ throw error;
7349
8297
  }
8298
+ this.store = new VectorStore(storePath, dimensions);
7350
8299
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
8300
+ this.database = new Database(dbPath);
8301
+ dbIsNew = true;
8302
+ }
8303
+ if (isGitRepo(this.projectRoot)) {
8304
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
8305
+ this.baseBranch = getBaseBranch(this.projectRoot);
8306
+ this.logger.branch("info", "Detected git repository", {
8307
+ currentBranch: this.currentBranch,
8308
+ baseBranch: this.baseBranch
8309
+ });
8310
+ } else {
8311
+ this.currentBranch = "default";
8312
+ this.baseBranch = "default";
8313
+ this.logger.branch("debug", "Not a git repository, using default branch");
7351
8314
  }
7352
- const dbPath = path7.join(this.indexPath, "codebase.db");
7353
- const dbIsNew = !(0, import_fs5.existsSync)(dbPath);
7354
- this.database = new Database(dbPath);
7355
8315
  if (this.checkForInterruptedIndexing()) {
7356
8316
  await this.recoverFromInterruptedIndexing();
7357
8317
  }
7358
8318
  if (dbIsNew && this.store.count() > 0) {
7359
8319
  this.migrateFromLegacyIndex();
7360
8320
  }
8321
+ this.loadFileHashCache();
7361
8322
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7362
8323
  if (!this.indexCompatibility.compatible) {
7363
8324
  this.logger.warn("Index compatibility issue detected", {
@@ -7366,18 +8327,6 @@ var Indexer = class {
7366
8327
  configuredProviderInfo: this.configuredProviderInfo
7367
8328
  });
7368
8329
  }
7369
- if (isGitRepo(this.projectRoot)) {
7370
- this.currentBranch = getBranchOrDefault(this.projectRoot);
7371
- this.baseBranch = getBaseBranch(this.projectRoot);
7372
- this.logger.branch("info", "Detected git repository", {
7373
- currentBranch: this.currentBranch,
7374
- baseBranch: this.baseBranch
7375
- });
7376
- } else {
7377
- this.currentBranch = "default";
7378
- this.baseBranch = "default";
7379
- this.logger.branch("debug", "Not a git repository, using default branch");
7380
- }
7381
8330
  if (this.config.indexing.autoGc) {
7382
8331
  await this.maybeRunAutoGc();
7383
8332
  }
@@ -7397,20 +8346,169 @@ var Indexer = class {
7397
8346
  }
7398
8347
  }
7399
8348
  if (shouldRunGc) {
7400
- await this.healthCheck();
8349
+ const result = await this.healthCheck();
8350
+ if (result.warning) {
8351
+ this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
8352
+ } else {
8353
+ this.database.deleteMetadata(STARTUP_WARNING_METADATA_KEY);
8354
+ }
7401
8355
  this.database.setMetadata("lastGcTimestamp", now.toString());
7402
8356
  }
7403
8357
  }
7404
8358
  async maybeRunOrphanGc() {
7405
- if (!this.database) return;
8359
+ if (!this.database) return null;
7406
8360
  const stats = this.database.getStats();
7407
- if (!stats) return;
8361
+ if (!stats) return null;
7408
8362
  const orphanCount = stats.embeddingCount - stats.chunkCount;
7409
8363
  if (orphanCount > this.config.indexing.gcOrphanThreshold) {
7410
- this.database.gcOrphanEmbeddings();
7411
- this.database.gcOrphanChunks();
8364
+ try {
8365
+ this.database.gcOrphanEmbeddings();
8366
+ this.database.gcOrphanChunks();
8367
+ } catch (error) {
8368
+ if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8369
+ return {
8370
+ resetCorruptedIndex: true,
8371
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8372
+ };
8373
+ }
8374
+ throw error;
8375
+ }
7412
8376
  this.database.setMetadata("lastGcTimestamp", Date.now().toString());
7413
8377
  }
8378
+ return null;
8379
+ }
8380
+ rebuildVectorStoreExcludingChunkIds(store, database, excludedChunkIds) {
8381
+ const excludedSet = new Set(excludedChunkIds);
8382
+ if (excludedSet.size === 0) {
8383
+ return;
8384
+ }
8385
+ const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8386
+ const storeBasePath = path8.join(this.indexPath, "vectors");
8387
+ const storeIndexPath = `${storeBasePath}.usearch`;
8388
+ const storeMetadataPath = `${storeBasePath}.meta.json`;
8389
+ const backupIndexPath = `${storeIndexPath}.bak`;
8390
+ const backupMetadataPath = `${storeMetadataPath}.bak`;
8391
+ let backedUpIndex = false;
8392
+ let backedUpMetadata = false;
8393
+ let rebuiltCount = 0;
8394
+ let skippedCount = 0;
8395
+ if ((0, import_fs6.existsSync)(backupIndexPath)) {
8396
+ (0, import_fs6.unlinkSync)(backupIndexPath);
8397
+ }
8398
+ if ((0, import_fs6.existsSync)(backupMetadataPath)) {
8399
+ (0, import_fs6.unlinkSync)(backupMetadataPath);
8400
+ }
8401
+ try {
8402
+ if ((0, import_fs6.existsSync)(storeIndexPath)) {
8403
+ (0, import_fs6.renameSync)(storeIndexPath, backupIndexPath);
8404
+ backedUpIndex = true;
8405
+ }
8406
+ if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8407
+ (0, import_fs6.renameSync)(storeMetadataPath, backupMetadataPath);
8408
+ backedUpMetadata = true;
8409
+ }
8410
+ store.clear();
8411
+ for (const { key, metadata } of retainedEntries) {
8412
+ const chunk = database.getChunk(key);
8413
+ if (!chunk) {
8414
+ skippedCount += 1;
8415
+ continue;
8416
+ }
8417
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8418
+ if (!embeddingBuffer) {
8419
+ skippedCount += 1;
8420
+ continue;
8421
+ }
8422
+ const vector = bufferToFloat32Array(embeddingBuffer);
8423
+ store.add(key, Array.from(vector), metadata);
8424
+ rebuiltCount += 1;
8425
+ }
8426
+ store.save();
8427
+ if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8428
+ (0, import_fs6.unlinkSync)(backupIndexPath);
8429
+ }
8430
+ if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8431
+ (0, import_fs6.unlinkSync)(backupMetadataPath);
8432
+ }
8433
+ this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8434
+ excludedChunks: excludedSet.size,
8435
+ rebuiltChunks: rebuiltCount,
8436
+ skippedChunks: skippedCount
8437
+ });
8438
+ } catch (error) {
8439
+ try {
8440
+ store.clear();
8441
+ } catch {
8442
+ }
8443
+ if ((0, import_fs6.existsSync)(storeIndexPath)) {
8444
+ (0, import_fs6.unlinkSync)(storeIndexPath);
8445
+ }
8446
+ if ((0, import_fs6.existsSync)(storeMetadataPath)) {
8447
+ (0, import_fs6.unlinkSync)(storeMetadataPath);
8448
+ }
8449
+ if (backedUpIndex && (0, import_fs6.existsSync)(backupIndexPath)) {
8450
+ (0, import_fs6.renameSync)(backupIndexPath, storeIndexPath);
8451
+ }
8452
+ if (backedUpMetadata && (0, import_fs6.existsSync)(backupMetadataPath)) {
8453
+ (0, import_fs6.renameSync)(backupMetadataPath, storeMetadataPath);
8454
+ }
8455
+ if (backedUpIndex || backedUpMetadata) {
8456
+ store.load();
8457
+ }
8458
+ throw error;
8459
+ }
8460
+ }
8461
+ getCorruptedIndexWarning(dbPath) {
8462
+ if (this.config.scope === "global") {
8463
+ return `Detected a corrupted shared global SQLite index at ${dbPath}. Automatic repair is disabled for global scope because it may delete other projects' index data. Remove or repair the shared index manually, then rerun index_codebase with force=true.`;
8464
+ }
8465
+ return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
8466
+ }
8467
+ async tryResetCorruptedIndex(stage, error) {
8468
+ if (!isSqliteCorruptionError(error)) {
8469
+ return false;
8470
+ }
8471
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8472
+ const warning = this.getCorruptedIndexWarning(dbPath);
8473
+ const errorMessage = getErrorMessage(error);
8474
+ if (this.config.scope === "global") {
8475
+ this.logger.error("Detected corrupted shared global index database", {
8476
+ stage,
8477
+ dbPath,
8478
+ error: errorMessage
8479
+ });
8480
+ throw new Error(`${warning} Original SQLite error: ${errorMessage}`);
8481
+ }
8482
+ this.logger.warn("Detected corrupted local index database, resetting local index", {
8483
+ stage,
8484
+ dbPath,
8485
+ error: errorMessage
8486
+ });
8487
+ this.store = null;
8488
+ this.invertedIndex = null;
8489
+ this.database?.close();
8490
+ this.database = null;
8491
+ this.indexCompatibility = null;
8492
+ this.fileHashCache.clear();
8493
+ 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")
8503
+ ];
8504
+ await Promise.all(resetPaths.map(async (targetPath) => {
8505
+ try {
8506
+ await import_fs6.promises.rm(targetPath, { recursive: true, force: true });
8507
+ } catch {
8508
+ }
8509
+ }));
8510
+ await import_fs6.promises.mkdir(this.indexPath, { recursive: true });
8511
+ return true;
7414
8512
  }
7415
8513
  migrateFromLegacyIndex() {
7416
8514
  if (!this.store || !this.database) return;
@@ -7434,7 +8532,7 @@ var Indexer = class {
7434
8532
  if (chunkDataBatch.length > 0) {
7435
8533
  this.database.upsertChunksBatch(chunkDataBatch);
7436
8534
  }
7437
- this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
8535
+ this.database.addChunksToBranchBatch(this.getBranchCatalogKey(), chunkIds);
7438
8536
  }
7439
8537
  loadIndexMetadata() {
7440
8538
  if (!this.database) return null;
@@ -7445,6 +8543,7 @@ var Indexer = class {
7445
8543
  embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
7446
8544
  embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
7447
8545
  embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
8546
+ embeddingStrategyVersion: this.loadStoredEmbeddingStrategyVersion() ?? EMBEDDING_STRATEGY_VERSION,
7448
8547
  createdAt: this.database.getMetadata("index.createdAt") ?? "",
7449
8548
  updatedAt: this.database.getMetadata("index.updatedAt") ?? ""
7450
8549
  };
@@ -7453,10 +8552,22 @@ var Indexer = class {
7453
8552
  if (!this.database) return;
7454
8553
  const now = (/* @__PURE__ */ new Date()).toISOString();
7455
8554
  const existingCreatedAt = this.database.getMetadata("index.createdAt");
8555
+ const completeProjectEmbeddingStrategyReset = !this.hasProjectForceReembedPending();
7456
8556
  this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
7457
8557
  this.database.setMetadata("index.embeddingProvider", provider.provider);
7458
8558
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
7459
8559
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8560
+ if (this.config.scope === "global") {
8561
+ if (completeProjectEmbeddingStrategyReset) {
8562
+ this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
8563
+ }
8564
+ this.database.setMetadata(this.getLegacyMigrationMetadataKey(), "done");
8565
+ if (completeProjectEmbeddingStrategyReset) {
8566
+ this.database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8567
+ }
8568
+ } else {
8569
+ this.database.setMetadata("index.embeddingStrategyVersion", EMBEDDING_STRATEGY_VERSION);
8570
+ }
7460
8571
  this.database.setMetadata("index.updatedAt", now);
7461
8572
  if (!existingCreatedAt) {
7462
8573
  this.database.setMetadata("index.createdAt", now);
@@ -7486,6 +8597,14 @@ var Indexer = class {
7486
8597
  storedMetadata
7487
8598
  };
7488
8599
  }
8600
+ if (storedMetadata.embeddingStrategyVersion !== EMBEDDING_STRATEGY_VERSION) {
8601
+ return {
8602
+ compatible: false,
8603
+ code: "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */,
8604
+ reason: `Embedding strategy mismatch: index was built with embedding strategy v${storedMetadata.embeddingStrategyVersion}, but the current code requires v${EMBEDDING_STRATEGY_VERSION}. Run index_codebase with force=true to rebuild cached embeddings.`,
8605
+ storedMetadata
8606
+ };
8607
+ }
7489
8608
  if (storedMetadata.embeddingProvider !== currentProvider) {
7490
8609
  this.logger.warn("Provider changed", {
7491
8610
  storedProvider: storedMetadata.embeddingProvider,
@@ -7533,6 +8652,10 @@ var Indexer = class {
7533
8652
  }
7534
8653
  async index(onProgress) {
7535
8654
  const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8655
+ const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
8656
+ const branchCatalogKey = this.getBranchCatalogKey();
8657
+ const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
8658
+ const failedForcedChunkIds = /* @__PURE__ */ new Set();
7536
8659
  if (!this.indexCompatibility?.compatible) {
7537
8660
  throw new Error(
7538
8661
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
@@ -7554,6 +8677,7 @@ var Indexer = class {
7554
8677
  skippedFiles: [],
7555
8678
  parseFailures: []
7556
8679
  };
8680
+ const failedBatchesForCurrentRun = [];
7557
8681
  onProgress?.({
7558
8682
  phase: "scanning",
7559
8683
  filesProcessed: 0,
@@ -7588,7 +8712,7 @@ var Indexer = class {
7588
8712
  unchangedFilePaths.add(f.path);
7589
8713
  this.logger.recordCacheHit();
7590
8714
  } else {
7591
- const content = await import_fs5.promises.readFile(f.path, "utf-8");
8715
+ const content = await import_fs6.promises.readFile(f.path, "utf-8");
7592
8716
  changedFiles.push({ path: f.path, content, hash: currentHash });
7593
8717
  this.logger.recordCacheMiss();
7594
8718
  }
@@ -7613,6 +8737,12 @@ var Indexer = class {
7613
8737
  const existingChunks = /* @__PURE__ */ new Map();
7614
8738
  const existingChunksByFile = /* @__PURE__ */ new Map();
7615
8739
  for (const { key, metadata } of store.getAllMetadata()) {
8740
+ if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8741
+ continue;
8742
+ }
8743
+ if (forceScopedReembed && scopedRoots && this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8744
+ continue;
8745
+ }
7616
8746
  existingChunks.set(key, metadata.hash);
7617
8747
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7618
8748
  fileChunks.add(key);
@@ -7634,7 +8764,7 @@ var Indexer = class {
7634
8764
  for (const parsed of parsedFiles) {
7635
8765
  currentFilePaths.add(parsed.path);
7636
8766
  if (parsed.chunks.length === 0) {
7637
- const relativePath = path7.relative(this.projectRoot, parsed.path);
8767
+ const relativePath = path8.relative(this.projectRoot, parsed.path);
7638
8768
  stats.parseFailures.push(relativePath);
7639
8769
  }
7640
8770
  let fileChunkCount = 0;
@@ -7670,7 +8800,10 @@ var Indexer = class {
7670
8800
  fileChunkCount++;
7671
8801
  continue;
7672
8802
  }
7673
- const text = createEmbeddingText(chunk, parsed.path);
8803
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text) => ({
8804
+ text,
8805
+ tokenCount: estimateTokens(text)
8806
+ }));
7674
8807
  const metadata = {
7675
8808
  filePath: parsed.path,
7676
8809
  startLine: chunk.startLine,
@@ -7680,10 +8813,38 @@ var Indexer = class {
7680
8813
  language: chunk.language,
7681
8814
  hash: contentHash
7682
8815
  };
7683
- pendingChunks.push({ id, text, content: chunk.content, contentHash, metadata });
8816
+ pendingChunks.push({
8817
+ id,
8818
+ texts,
8819
+ storageText: createPendingChunkStorageText(texts),
8820
+ content: chunk.content,
8821
+ contentHash,
8822
+ metadata
8823
+ });
7684
8824
  fileChunkCount++;
7685
8825
  }
7686
8826
  }
8827
+ const retryableFailedChunks = this.collectRetryableFailedChunks(
8828
+ currentFileHashes,
8829
+ unchangedFilePaths,
8830
+ getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)
8831
+ );
8832
+ const retryableFailedAttemptCounts = /* @__PURE__ */ new Map();
8833
+ const retryableChunksWithExistingData = /* @__PURE__ */ new Set();
8834
+ if (retryableFailedChunks.length > 0) {
8835
+ const pendingChunkIds = new Set(pendingChunks.map((chunk) => chunk.id));
8836
+ for (const { chunk, attemptCount } of retryableFailedChunks) {
8837
+ retryableFailedAttemptCounts.set(chunk.id, attemptCount);
8838
+ if (existingChunks.has(chunk.id)) {
8839
+ retryableChunksWithExistingData.add(chunk.id);
8840
+ }
8841
+ if (!pendingChunkIds.has(chunk.id)) {
8842
+ pendingChunks.push(chunk);
8843
+ pendingChunkIds.add(chunk.id);
8844
+ currentChunkIds.add(chunk.id);
8845
+ }
8846
+ }
8847
+ }
7687
8848
  if (chunkDataBatch.length > 0) {
7688
8849
  database.upsertChunksBatch(chunkDataBatch);
7689
8850
  }
@@ -7712,17 +8873,20 @@ var Indexer = class {
7712
8873
  fileSymbols.push(symbol);
7713
8874
  allSymbolIds.add(symbolId);
7714
8875
  }
8876
+ const fileLanguage = parsed.chunks[0]?.language;
8877
+ const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
8878
+ const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
7715
8879
  const symbolsByName = /* @__PURE__ */ new Map();
7716
8880
  for (const symbol of fileSymbols) {
7717
- const existing = symbolsByName.get(symbol.name) ?? [];
8881
+ const key = normalizeSymbolKey(symbol.name);
8882
+ const existing = symbolsByName.get(key) ?? [];
7718
8883
  existing.push(symbol);
7719
- symbolsByName.set(symbol.name, existing);
8884
+ symbolsByName.set(key, existing);
7720
8885
  }
7721
8886
  if (fileSymbols.length > 0) {
7722
8887
  database.upsertSymbolsBatch(fileSymbols);
7723
8888
  symbolsByFile.set(parsed.path, fileSymbols);
7724
8889
  }
7725
- const fileLanguage = parsed.chunks[0]?.language;
7726
8890
  if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
7727
8891
  const callSites = extractCalls(changedFile.content, fileLanguage);
7728
8892
  if (callSites.length === 0) continue;
@@ -7747,7 +8911,7 @@ var Indexer = class {
7747
8911
  if (edges.length > 0) {
7748
8912
  database.upsertCallEdgesBatch(edges);
7749
8913
  for (const edge of edges) {
7750
- const candidates = symbolsByName.get(edge.targetName);
8914
+ const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
7751
8915
  if (candidates && candidates.length === 1) {
7752
8916
  database.resolveCallEdge(edge.id, candidates[0].id);
7753
8917
  }
@@ -7760,14 +8924,20 @@ var Indexer = class {
7760
8924
  allSymbolIds.add(sym.id);
7761
8925
  }
7762
8926
  }
7763
- let removedCount = 0;
8927
+ const removedChunkIds = [];
7764
8928
  for (const [chunkId] of existingChunks) {
7765
8929
  if (!currentChunkIds.has(chunkId)) {
7766
- store.remove(chunkId);
8930
+ removedChunkIds.push(chunkId);
8931
+ }
8932
+ }
8933
+ if (removedChunkIds.length > 0) {
8934
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkIds);
8935
+ for (const chunkId of removedChunkIds) {
7767
8936
  invertedIndex.removeChunk(chunkId);
7768
- removedCount++;
7769
8937
  }
8938
+ database.deleteChunksByIds(removedChunkIds);
7770
8939
  }
8940
+ const removedCount = removedChunkIds.length;
7771
8941
  stats.totalChunks = pendingChunks.length;
7772
8942
  stats.existingChunks = currentChunkIds.size - pendingChunks.length;
7773
8943
  stats.removedChunks = removedCount;
@@ -7779,12 +8949,20 @@ var Indexer = class {
7779
8949
  removed: removedCount
7780
8950
  });
7781
8951
  if (pendingChunks.length === 0 && removedCount === 0) {
7782
- database.clearBranch(this.currentBranch);
7783
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7784
- database.clearBranchSymbols(this.currentBranch);
7785
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
7786
- this.fileHashCache = currentFileHashes;
7787
- this.saveFileHashCache();
8952
+ database.clearBranch(branchCatalogKey);
8953
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8954
+ database.clearBranchSymbols(branchCatalogKey);
8955
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8956
+ if (scopedRoots) {
8957
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8958
+ this.clearScopedFailedBatches(scopedRoots);
8959
+ } else {
8960
+ this.fileHashCache = currentFileHashes;
8961
+ this.saveFileHashCache();
8962
+ this.saveFailedBatches([]);
8963
+ }
8964
+ this.saveIndexMetadata(configuredProviderInfo);
8965
+ this.indexCompatibility = { compatible: true };
7788
8966
  stats.durationMs = Date.now() - startTime;
7789
8967
  onProgress?.({
7790
8968
  phase: "complete",
@@ -7797,14 +8975,22 @@ var Indexer = class {
7797
8975
  return stats;
7798
8976
  }
7799
8977
  if (pendingChunks.length === 0) {
7800
- database.clearBranch(this.currentBranch);
7801
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7802
- database.clearBranchSymbols(this.currentBranch);
7803
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
8978
+ database.clearBranch(branchCatalogKey);
8979
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8980
+ database.clearBranchSymbols(branchCatalogKey);
8981
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7804
8982
  store.save();
7805
8983
  invertedIndex.save();
7806
- this.fileHashCache = currentFileHashes;
7807
- this.saveFileHashCache();
8984
+ if (scopedRoots) {
8985
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8986
+ this.clearScopedFailedBatches(scopedRoots);
8987
+ } else {
8988
+ this.fileHashCache = currentFileHashes;
8989
+ this.saveFileHashCache();
8990
+ this.saveFailedBatches([]);
8991
+ }
8992
+ this.saveIndexMetadata(configuredProviderInfo);
8993
+ this.indexCompatibility = { compatible: true };
7808
8994
  stats.durationMs = Date.now() - startTime;
7809
8995
  onProgress?.({
7810
8996
  phase: "complete",
@@ -7825,8 +9011,9 @@ var Indexer = class {
7825
9011
  });
7826
9012
  const allContentHashes = pendingChunks.map((c) => c.contentHash);
7827
9013
  const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
7828
- const chunksNeedingEmbedding = pendingChunks.filter((c) => missingHashes.has(c.contentHash));
7829
- const chunksWithExistingEmbedding = pendingChunks.filter((c) => !missingHashes.has(c.contentHash));
9014
+ const forcedReembedChunkIds = forceScopedReembed ? new Set(pendingChunks.map((chunk) => chunk.id)) : /* @__PURE__ */ new Set();
9015
+ const chunksNeedingEmbedding = pendingChunks.filter((c) => forcedReembedChunkIds.has(c.id) || missingHashes.has(c.contentHash));
9016
+ const chunksWithExistingEmbedding = pendingChunks.filter((c) => !forcedReembedChunkIds.has(c.id) && !missingHashes.has(c.contentHash));
7830
9017
  this.logger.cache("info", "Embedding cache lookup", {
7831
9018
  needsEmbedding: chunksNeedingEmbedding.length,
7832
9019
  fromCache: chunksWithExistingEmbedding.length
@@ -7848,17 +9035,24 @@ var Indexer = class {
7848
9035
  interval: providerRateLimits.intervalMs,
7849
9036
  intervalCap: providerRateLimits.concurrency
7850
9037
  });
7851
- const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
9038
+ const pendingChunksById = new Map(chunksNeedingEmbedding.map((chunk) => [chunk.id, chunk]));
9039
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9040
+ const completedChunkIds = /* @__PURE__ */ new Set();
9041
+ const failedChunkIds = /* @__PURE__ */ new Set();
9042
+ const requestBatches = createPendingEmbeddingRequestBatches(
9043
+ chunksNeedingEmbedding,
9044
+ getDynamicBatchOptions(configuredProviderInfo)
9045
+ );
7852
9046
  let rateLimitBackoffMs = 0;
7853
- for (const batch of dynamicBatches) {
9047
+ for (const requestBatch of requestBatches) {
7854
9048
  queue.add(async () => {
7855
9049
  if (rateLimitBackoffMs > 0) {
7856
- await new Promise((resolve7) => setTimeout(resolve7, rateLimitBackoffMs));
9050
+ await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
7857
9051
  }
7858
9052
  try {
7859
9053
  const result = await pRetry(
7860
9054
  async () => {
7861
- const texts = batch.map((c) => c.text);
9055
+ const texts = requestBatch.map((request) => request.text);
7862
9056
  return provider.embedBatch(texts);
7863
9057
  },
7864
9058
  {
@@ -7888,29 +9082,82 @@ var Indexer = class {
7888
9082
  if (rateLimitBackoffMs > 0) {
7889
9083
  rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2e3);
7890
9084
  }
7891
- const items = batch.map((chunk, idx) => ({
7892
- id: chunk.id,
7893
- vector: result.embeddings[idx],
7894
- metadata: chunk.metadata
7895
- }));
7896
- store.addBatch(items);
7897
- const embeddingBatchItems = batch.map((chunk, i) => ({
7898
- contentHash: chunk.contentHash,
7899
- embedding: float32ArrayToBuffer(result.embeddings[i]),
7900
- chunkText: chunk.text,
7901
- model: configuredProviderInfo.modelInfo.model
7902
- }));
7903
- database.upsertEmbeddingsBatch(embeddingBatchItems);
7904
- for (const chunk of batch) {
7905
- invertedIndex.removeChunk(chunk.id);
7906
- invertedIndex.addChunk(chunk.id, chunk.content);
9085
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9086
+ requestBatch.forEach((request, idx) => {
9087
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9088
+ return;
9089
+ }
9090
+ const vector = result.embeddings[idx];
9091
+ if (!vector) {
9092
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9093
+ }
9094
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9095
+ parts[request.partIndex] = {
9096
+ vector,
9097
+ tokenCount: request.tokenCount
9098
+ };
9099
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9100
+ touchedChunkIds.add(request.chunk.id);
9101
+ });
9102
+ const pooledResults = [];
9103
+ for (const chunkId of touchedChunkIds) {
9104
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9105
+ continue;
9106
+ }
9107
+ const chunk = pendingChunksById.get(chunkId);
9108
+ if (!chunk) {
9109
+ continue;
9110
+ }
9111
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9112
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9113
+ continue;
9114
+ }
9115
+ const orderedParts = parts;
9116
+ pooledResults.push({
9117
+ chunk,
9118
+ vector: poolEmbeddingVectors(
9119
+ orderedParts.map((part) => part.vector),
9120
+ orderedParts.map((part) => part.tokenCount)
9121
+ )
9122
+ });
9123
+ }
9124
+ if (pooledResults.length > 0) {
9125
+ const items = pooledResults.map(({ chunk, vector }) => ({
9126
+ id: chunk.id,
9127
+ vector,
9128
+ metadata: chunk.metadata
9129
+ }));
9130
+ store.addBatch(items);
9131
+ const embeddingBatchItems = pooledResults.map(({ chunk, vector }) => ({
9132
+ contentHash: chunk.contentHash,
9133
+ embedding: float32ArrayToBuffer(vector),
9134
+ chunkText: chunk.storageText,
9135
+ model: configuredProviderInfo.modelInfo.model
9136
+ }));
9137
+ try {
9138
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
9139
+ } catch (dbError) {
9140
+ this.rebuildVectorStoreExcludingChunkIds(
9141
+ store,
9142
+ database,
9143
+ pooledResults.map(({ chunk }) => chunk.id)
9144
+ );
9145
+ throw dbError;
9146
+ }
9147
+ for (const { chunk } of pooledResults) {
9148
+ invertedIndex.removeChunk(chunk.id);
9149
+ invertedIndex.addChunk(chunk.id, chunk.content);
9150
+ completedChunkIds.add(chunk.id);
9151
+ embeddingPartsByChunk.delete(chunk.id);
9152
+ }
9153
+ stats.indexedChunks += pooledResults.length;
9154
+ this.logger.recordChunksEmbedded(pooledResults.length);
7907
9155
  }
7908
- stats.indexedChunks += batch.length;
7909
9156
  stats.tokensUsed += result.totalTokensUsed;
7910
- this.logger.recordChunksEmbedded(batch.length);
7911
9157
  this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
7912
9158
  this.logger.embedding("debug", `Embedded batch`, {
7913
- batchSize: batch.length,
9159
+ batchSize: pooledResults.length,
9160
+ requestCount: requestBatch.length,
7914
9161
  tokens: result.totalTokensUsed
7915
9162
  });
7916
9163
  onProgress?.({
@@ -7921,17 +9168,49 @@ var Indexer = class {
7921
9168
  totalChunks: pendingChunks.length
7922
9169
  });
7923
9170
  } catch (error) {
7924
- stats.failedChunks += batch.length;
7925
- this.addFailedBatch(batch, getErrorMessage(error));
9171
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
9172
+ const failureMessage = getErrorMessage(error);
9173
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9174
+ for (const chunk of failedChunks) {
9175
+ if (!failedChunkIds.has(chunk.id)) {
9176
+ failedChunkIds.add(chunk.id);
9177
+ stats.failedChunks += 1;
9178
+ }
9179
+ if (forceScopedReembed) {
9180
+ failedForcedChunkIds.add(chunk.id);
9181
+ }
9182
+ embeddingPartsByChunk.delete(chunk.id);
9183
+ const existingFailedBatchIndex = failedBatchesForCurrentRun.findIndex(
9184
+ (failedBatch2) => failedBatch2.chunks[0]?.id === chunk.id
9185
+ );
9186
+ const existingFailedBatch = existingFailedBatchIndex === -1 ? void 0 : failedBatchesForCurrentRun[existingFailedBatchIndex];
9187
+ const failedBatch = {
9188
+ chunks: [chunk],
9189
+ error: failureMessage,
9190
+ attemptCount: (existingFailedBatch?.attemptCount ?? retryableFailedAttemptCounts.get(chunk.id) ?? 0) + 1,
9191
+ lastAttempt: failureTimestamp
9192
+ };
9193
+ if (existingFailedBatchIndex === -1) {
9194
+ failedBatchesForCurrentRun.push(failedBatch);
9195
+ } else {
9196
+ failedBatchesForCurrentRun[existingFailedBatchIndex] = failedBatch;
9197
+ }
9198
+ }
7926
9199
  this.logger.recordEmbeddingError();
7927
9200
  this.logger.embedding("error", `Failed to embed batch after retries`, {
7928
- batchSize: batch.length,
7929
- error: getErrorMessage(error)
9201
+ batchSize: failedChunks.length,
9202
+ requestCount: requestBatch.length,
9203
+ error: failureMessage
7930
9204
  });
7931
9205
  }
7932
9206
  });
7933
9207
  }
7934
9208
  await queue.onIdle();
9209
+ if (scopedRoots) {
9210
+ this.saveScopedFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun), scopedRoots);
9211
+ } else {
9212
+ this.saveFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun));
9213
+ }
7935
9214
  onProgress?.({
7936
9215
  phase: "storing",
7937
9216
  filesProcessed: files.length,
@@ -7939,18 +9218,48 @@ var Indexer = class {
7939
9218
  chunksProcessed: stats.indexedChunks,
7940
9219
  totalChunks: pendingChunks.length
7941
9220
  });
7942
- database.clearBranch(this.currentBranch);
7943
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7944
- database.clearBranchSymbols(this.currentBranch);
7945
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
9221
+ const branchChunkIds = Array.from(currentChunkIds).filter(
9222
+ (chunkId) => {
9223
+ const isNewlyFailed = failedChunkIds.has(chunkId) && !retryableChunksWithExistingData.has(chunkId);
9224
+ const isForcedFailed = forceScopedReembed && failedForcedChunkIds.has(chunkId);
9225
+ return !isNewlyFailed && !isForcedFailed;
9226
+ }
9227
+ );
9228
+ database.clearBranch(branchCatalogKey);
9229
+ database.addChunksToBranchBatch(branchCatalogKey, branchChunkIds);
9230
+ database.clearBranchSymbols(branchCatalogKey);
9231
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7946
9232
  store.save();
7947
9233
  invertedIndex.save();
7948
- this.fileHashCache = currentFileHashes;
7949
- this.saveFileHashCache();
9234
+ if (scopedRoots) {
9235
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
9236
+ } else {
9237
+ this.fileHashCache = currentFileHashes;
9238
+ this.saveFileHashCache();
9239
+ }
7950
9240
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
7951
- await this.maybeRunOrphanGc();
9241
+ const gcReset = await this.maybeRunOrphanGc();
9242
+ if (gcReset) {
9243
+ stats.durationMs = Date.now() - startTime;
9244
+ stats.warning = gcReset.warning;
9245
+ stats.resetCorruptedIndex = true;
9246
+ this.logger.recordIndexingEnd();
9247
+ this.logger.warn("Indexing ended after resetting corrupted local index during automatic GC", {
9248
+ files: stats.totalFiles,
9249
+ indexed: stats.indexedChunks,
9250
+ existing: stats.existingChunks,
9251
+ removed: stats.removedChunks,
9252
+ failed: stats.failedChunks,
9253
+ tokens: stats.tokensUsed,
9254
+ durationMs: stats.durationMs
9255
+ });
9256
+ return stats;
9257
+ }
7952
9258
  }
7953
9259
  stats.durationMs = Date.now() - startTime;
9260
+ if (forceScopedReembed && failedForcedChunkIds.size === 0) {
9261
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9262
+ }
7954
9263
  this.saveIndexMetadata(configuredProviderInfo);
7955
9264
  this.indexCompatibility = { compatible: true };
7956
9265
  this.logger.recordIndexingEnd();
@@ -8079,27 +9388,30 @@ var Indexer = class {
8079
9388
  const keywordResults = await this.keywordSearch(query, maxResults * 4);
8080
9389
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
8081
9390
  let branchChunkIds = null;
8082
- if (filterByBranch && this.currentBranch !== "default") {
8083
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9391
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9392
+ branchChunkIds = new Set(
9393
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9394
+ );
8084
9395
  }
8085
9396
  const prefilterStartTime = import_perf_hooks.performance.now();
8086
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9397
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9398
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8087
9399
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8088
9400
  const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
8089
- const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
8090
- const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
9401
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9402
+ const keywordCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
8091
9403
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
8092
- if (branchChunkIds && branchChunkIds.size === 0) {
9404
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8093
9405
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8094
9406
  branch: this.currentBranch
8095
9407
  });
8096
9408
  }
8097
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9409
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8098
9410
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8099
9411
  branch: this.currentBranch
8100
9412
  });
8101
9413
  }
8102
- if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
9414
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
8103
9415
  this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
8104
9416
  branch: this.currentBranch
8105
9417
  });
@@ -8152,26 +9464,13 @@ var Indexer = class {
8152
9464
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
8153
9465
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
8154
9466
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
8155
- const baseFiltered = tiered.filter((r) => {
8156
- if (r.score < this.config.search.minScore) return false;
8157
- if (options?.fileType) {
8158
- const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
8159
- if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
8160
- }
8161
- if (options?.directory) {
8162
- const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
8163
- if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
8164
- }
8165
- if (options?.chunkType) {
8166
- if (r.metadata.chunkType !== options.chunkType) return false;
8167
- }
8168
- return true;
8169
- });
9467
+ const baseFiltered = tiered.filter((r) => matchesSearchFilters(r, options, this.config.search.minScore));
8170
9468
  const implementationOnly = baseFiltered.filter(
8171
9469
  (r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
8172
9470
  );
8173
9471
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
8174
- const finalResults = filtered;
9472
+ const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore)).slice(0, maxResults) : [];
9473
+ const finalResults = filtered.length > 0 ? filtered : identifierFallback;
8175
9474
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
8176
9475
  this.logger.recordSearch(totalSearchMs, {
8177
9476
  embeddingMs,
@@ -8197,7 +9496,7 @@ var Indexer = class {
8197
9496
  let contextEndLine = r.metadata.endLine;
8198
9497
  if (!metadataOnly && this.config.search.includeContext) {
8199
9498
  try {
8200
- const fileContent = await import_fs5.promises.readFile(
9499
+ const fileContent = await import_fs6.promises.readFile(
8201
9500
  r.metadata.filePath,
8202
9501
  "utf-8"
8203
9502
  );
@@ -8241,7 +9540,8 @@ var Indexer = class {
8241
9540
  return results.slice(0, limit);
8242
9541
  }
8243
9542
  async getStatus() {
8244
- const { store, configuredProviderInfo } = await this.ensureInitialized();
9543
+ const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9544
+ const failedBatchesCount = this.getFailedBatchesCount();
8245
9545
  return {
8246
9546
  indexed: store.count() > 0,
8247
9547
  vectorCount: store.count(),
@@ -8250,23 +9550,86 @@ var Indexer = class {
8250
9550
  indexPath: this.indexPath,
8251
9551
  currentBranch: this.currentBranch,
8252
9552
  baseBranch: this.baseBranch,
8253
- compatibility: this.indexCompatibility
9553
+ compatibility: this.indexCompatibility,
9554
+ failedBatchesCount,
9555
+ failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
9556
+ warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
8254
9557
  };
8255
9558
  }
8256
9559
  async clearIndex() {
8257
9560
  const { store, invertedIndex, database } = await this.ensureInitialized();
9561
+ if (this.config.scope === "global") {
9562
+ store.load();
9563
+ invertedIndex.load();
9564
+ this.loadFileHashCache();
9565
+ const roots = this.getScopedRoots();
9566
+ const compatibility = this.checkCompatibility();
9567
+ const allMetadata = store.getAllMetadata();
9568
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
9569
+ if (!compatibility.compatible && hasForeignData) {
9570
+ if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
9571
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9572
+ this.clearScopedFileHashCache(roots);
9573
+ this.clearScopedFailedBatches(roots);
9574
+ database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
9575
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9576
+ this.indexCompatibility = { compatible: true };
9577
+ return;
9578
+ }
9579
+ throw new Error(
9580
+ `Global index compatibility reset is unsafe because the shared index contains files from other projects. The current global index cannot be force-rebuilt for ${this.projectRoot} without deleting other repositories' indexed data. Use scope="project" for isolated rebuilds, or manually delete the shared global index if you intend to rebuild all projects.`
9581
+ );
9582
+ }
9583
+ if (!hasForeignData) {
9584
+ store.clear();
9585
+ store.save();
9586
+ invertedIndex.clear();
9587
+ invertedIndex.save();
9588
+ this.fileHashCache.clear();
9589
+ this.saveFileHashCache();
9590
+ database.clearAllIndexedData();
9591
+ this.saveFailedBatches([]);
9592
+ database.deleteMetadata("index.version");
9593
+ database.deleteMetadata("index.embeddingProvider");
9594
+ database.deleteMetadata("index.embeddingModel");
9595
+ database.deleteMetadata("index.embeddingDimensions");
9596
+ database.deleteMetadata("index.embeddingStrategyVersion");
9597
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9598
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9599
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
9600
+ database.deleteMetadata("index.createdAt");
9601
+ database.deleteMetadata("index.updatedAt");
9602
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
9603
+ return;
9604
+ }
9605
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9606
+ this.clearScopedFileHashCache(roots);
9607
+ this.clearScopedFailedBatches(roots);
9608
+ this.indexCompatibility = compatibility;
9609
+ return;
9610
+ }
9611
+ const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9612
+ if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9613
+ throw new Error(
9614
+ "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9615
+ );
9616
+ }
8258
9617
  store.clear();
8259
9618
  store.save();
8260
9619
  invertedIndex.clear();
8261
9620
  invertedIndex.save();
8262
9621
  this.fileHashCache.clear();
8263
9622
  this.saveFileHashCache();
8264
- database.clearBranch(this.currentBranch);
8265
- database.clearBranchSymbols(this.currentBranch);
9623
+ database.clearAllIndexedData();
9624
+ this.saveFailedBatches([]);
8266
9625
  database.deleteMetadata("index.version");
8267
9626
  database.deleteMetadata("index.embeddingProvider");
8268
9627
  database.deleteMetadata("index.embeddingModel");
8269
9628
  database.deleteMetadata("index.embeddingDimensions");
9629
+ database.deleteMetadata("index.embeddingStrategyVersion");
9630
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9631
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9632
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
8270
9633
  database.deleteMetadata("index.createdAt");
8271
9634
  database.deleteMetadata("index.updatedAt");
8272
9635
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
@@ -8282,28 +9645,61 @@ var Indexer = class {
8282
9645
  filePathsToChunkKeys.set(metadata.filePath, existing);
8283
9646
  }
8284
9647
  const removedFilePaths = [];
8285
- let removedCount = 0;
9648
+ const removedChunkKeys = [];
9649
+ const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8286
9650
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8287
- if (!(0, import_fs5.existsSync)(filePath)) {
9651
+ if (!(0, import_fs6.existsSync)(filePath)) {
9652
+ chunkKeysByRemovedFile.set(filePath, chunkKeys);
8288
9653
  for (const key of chunkKeys) {
8289
- store.remove(key);
8290
- invertedIndex.removeChunk(key);
8291
- removedCount++;
9654
+ removedChunkKeys.push(key);
8292
9655
  }
8293
- database.deleteChunksByFile(filePath);
8294
- database.deleteCallEdgesByFile(filePath);
8295
- database.deleteSymbolsByFile(filePath);
8296
9656
  removedFilePaths.push(filePath);
8297
9657
  }
8298
9658
  }
9659
+ if (removedChunkKeys.length > 0) {
9660
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkKeys);
9661
+ for (const key of removedChunkKeys) {
9662
+ invertedIndex.removeChunk(key);
9663
+ }
9664
+ }
9665
+ for (const filePath of removedFilePaths) {
9666
+ const fileChunkKeys = chunkKeysByRemovedFile.get(filePath) ?? [];
9667
+ if (fileChunkKeys.length > 0) {
9668
+ database.deleteChunksByIds(fileChunkKeys);
9669
+ }
9670
+ database.deleteCallEdgesByFile(filePath);
9671
+ database.deleteSymbolsByFile(filePath);
9672
+ }
9673
+ const removedCount = removedChunkKeys.length;
8299
9674
  if (removedCount > 0) {
8300
9675
  store.save();
8301
9676
  invertedIndex.save();
8302
9677
  }
8303
- const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
8304
- const gcOrphanChunks = database.gcOrphanChunks();
8305
- const gcOrphanSymbols = database.gcOrphanSymbols();
8306
- const gcOrphanCallEdges = database.gcOrphanCallEdges();
9678
+ let gcOrphanEmbeddings;
9679
+ let gcOrphanChunks;
9680
+ let gcOrphanSymbols;
9681
+ let gcOrphanCallEdges;
9682
+ try {
9683
+ gcOrphanEmbeddings = database.gcOrphanEmbeddings();
9684
+ gcOrphanChunks = database.gcOrphanChunks();
9685
+ gcOrphanSymbols = database.gcOrphanSymbols();
9686
+ gcOrphanCallEdges = database.gcOrphanCallEdges();
9687
+ } catch (error) {
9688
+ if (!await this.tryResetCorruptedIndex("running index health check", error)) {
9689
+ throw error;
9690
+ }
9691
+ await this.ensureInitialized();
9692
+ return {
9693
+ removed: 0,
9694
+ filePaths: [],
9695
+ gcOrphanEmbeddings: 0,
9696
+ gcOrphanChunks: 0,
9697
+ gcOrphanSymbols: 0,
9698
+ gcOrphanCallEdges: 0,
9699
+ resetCorruptedIndex: true,
9700
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9701
+ };
9702
+ }
8307
9703
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
8308
9704
  this.logger.gc("info", "Health check complete", {
8309
9705
  removedStale: removedCount,
@@ -8314,8 +9710,12 @@ var Indexer = class {
8314
9710
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8315
9711
  }
8316
9712
  async retryFailedBatches() {
8317
- const { store, provider, invertedIndex } = await this.ensureInitialized();
8318
- const failedBatches = this.loadFailedBatches();
9713
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9714
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
9715
+ const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
9716
+ const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
9717
+ const { scoped: scopedFailedBatches, retained: retainedFailedBatches } = roots ? this.partitionFailedBatches(roots, maxChunkTokens) : { scoped: this.loadFailedBatches(maxChunkTokens), retained: [] };
9718
+ const failedBatches = scopedFailedBatches;
8319
9719
  if (failedBatches.length === 0) {
8320
9720
  return { succeeded: 0, failed: 0, remaining: 0 };
8321
9721
  }
@@ -8323,49 +9723,170 @@ var Indexer = class {
8323
9723
  let failed = 0;
8324
9724
  const stillFailing = [];
8325
9725
  for (const batch of failedBatches) {
9726
+ const batchChunksById = new Map(batch.chunks.map((chunk) => [chunk.id, chunk]));
9727
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9728
+ const completedChunkIds = /* @__PURE__ */ new Set();
9729
+ const failedChunkIds = /* @__PURE__ */ new Set();
9730
+ const failedChunksForBatch = /* @__PURE__ */ new Map();
9731
+ const pooledResults = [];
8326
9732
  try {
8327
- const result = await pRetry(
8328
- async () => {
8329
- const texts = batch.chunks.map((c) => c.text);
8330
- return provider.embedBatch(texts);
8331
- },
8332
- {
8333
- retries: this.config.indexing.retries,
8334
- minTimeout: this.config.indexing.retryDelayMs
8335
- }
9733
+ const requestBatches = createPendingEmbeddingRequestBatches(
9734
+ batch.chunks,
9735
+ getDynamicBatchOptions(configuredProviderInfo)
8336
9736
  );
8337
- const items = batch.chunks.map((chunk, idx) => ({
9737
+ for (const requestBatch of requestBatches) {
9738
+ try {
9739
+ const result = await pRetry(
9740
+ async () => {
9741
+ const texts = requestBatch.map((request) => request.text);
9742
+ return provider.embedBatch(texts);
9743
+ },
9744
+ {
9745
+ retries: this.config.indexing.retries,
9746
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
9747
+ maxTimeout: providerRateLimits.maxRetryMs,
9748
+ factor: 2,
9749
+ shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError)
9750
+ }
9751
+ );
9752
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9753
+ requestBatch.forEach((request, idx) => {
9754
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9755
+ return;
9756
+ }
9757
+ const vector = result.embeddings[idx];
9758
+ if (!vector) {
9759
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9760
+ }
9761
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9762
+ parts[request.partIndex] = {
9763
+ vector,
9764
+ tokenCount: request.tokenCount
9765
+ };
9766
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9767
+ touchedChunkIds.add(request.chunk.id);
9768
+ });
9769
+ for (const chunkId of touchedChunkIds) {
9770
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9771
+ continue;
9772
+ }
9773
+ const chunk = batchChunksById.get(chunkId);
9774
+ if (!chunk) {
9775
+ continue;
9776
+ }
9777
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9778
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9779
+ continue;
9780
+ }
9781
+ const orderedParts = parts;
9782
+ pooledResults.push({
9783
+ chunk,
9784
+ vector: poolEmbeddingVectors(
9785
+ orderedParts.map((part) => part.vector),
9786
+ orderedParts.map((part) => part.tokenCount)
9787
+ )
9788
+ });
9789
+ }
9790
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
9791
+ } catch (error) {
9792
+ const failureMessage = String(error);
9793
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9794
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id) && !failedChunkIds.has(chunk.id));
9795
+ for (const chunk of failedChunks) {
9796
+ failedChunkIds.add(chunk.id);
9797
+ embeddingPartsByChunk.delete(chunk.id);
9798
+ failedChunksForBatch.set(chunk.id, {
9799
+ chunks: [chunk],
9800
+ attemptCount: batch.attemptCount + 1,
9801
+ lastAttempt: failureTimestamp,
9802
+ error: failureMessage
9803
+ });
9804
+ }
9805
+ failed += failedChunks.length;
9806
+ this.logger.recordEmbeddingError();
9807
+ }
9808
+ }
9809
+ const successfulResults = pooledResults.filter(({ chunk }) => !failedChunkIds.has(chunk.id));
9810
+ const items = successfulResults.map(({ chunk, vector }) => ({
8338
9811
  id: chunk.id,
8339
- vector: result.embeddings[idx],
9812
+ vector,
8340
9813
  metadata: chunk.metadata
8341
9814
  }));
8342
- store.addBatch(items);
8343
- for (const chunk of batch.chunks) {
9815
+ if (items.length > 0) {
9816
+ store.addBatch(items);
9817
+ }
9818
+ if (successfulResults.length > 0) {
9819
+ try {
9820
+ database.upsertEmbeddingsBatch(
9821
+ successfulResults.map(({ chunk, vector }) => ({
9822
+ contentHash: chunk.contentHash,
9823
+ embedding: float32ArrayToBuffer(vector),
9824
+ chunkText: chunk.storageText,
9825
+ model: configuredProviderInfo.modelInfo.model
9826
+ }))
9827
+ );
9828
+ } catch (dbError) {
9829
+ this.rebuildVectorStoreExcludingChunkIds(
9830
+ store,
9831
+ database,
9832
+ successfulResults.map(({ chunk }) => chunk.id)
9833
+ );
9834
+ throw dbError;
9835
+ }
9836
+ }
9837
+ for (const { chunk } of successfulResults) {
8344
9838
  invertedIndex.removeChunk(chunk.id);
8345
9839
  invertedIndex.addChunk(chunk.id, chunk.content);
9840
+ completedChunkIds.add(chunk.id);
9841
+ embeddingPartsByChunk.delete(chunk.id);
8346
9842
  }
8347
- this.logger.recordChunksEmbedded(batch.chunks.length);
8348
- this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
8349
- succeeded += batch.chunks.length;
9843
+ database.addChunksToBranchBatch(
9844
+ this.getBranchCatalogKey(),
9845
+ successfulResults.map(({ chunk }) => chunk.id)
9846
+ );
9847
+ this.logger.recordChunksEmbedded(successfulResults.length);
9848
+ succeeded += successfulResults.length;
9849
+ stillFailing.push(...failedChunksForBatch.values());
8350
9850
  } catch (error) {
8351
- failed += batch.chunks.length;
9851
+ const failureMessage = getErrorMessage(error);
9852
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9853
+ const unaccountedChunks = batch.chunks.filter(
9854
+ (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
9855
+ );
9856
+ for (const chunk of unaccountedChunks) {
9857
+ failedChunksForBatch.set(chunk.id, {
9858
+ chunks: [chunk],
9859
+ attemptCount: batch.attemptCount + 1,
9860
+ lastAttempt: failureTimestamp,
9861
+ error: failureMessage
9862
+ });
9863
+ }
9864
+ failed += unaccountedChunks.length;
8352
9865
  this.logger.recordEmbeddingError();
8353
- stillFailing.push({
8354
- ...batch,
8355
- attemptCount: batch.attemptCount + 1,
8356
- lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
8357
- error: String(error)
8358
- });
9866
+ stillFailing.push(...coalesceFailedBatches(Array.from(failedChunksForBatch.values())));
8359
9867
  }
8360
9868
  }
8361
- this.saveFailedBatches(stillFailing);
9869
+ const persistedStillFailing = coalesceFailedBatches(stillFailing);
9870
+ if (roots) {
9871
+ this.saveFailedBatches([...retainedFailedBatches, ...persistedStillFailing]);
9872
+ } else {
9873
+ this.saveFailedBatches(persistedStillFailing);
9874
+ }
8362
9875
  if (succeeded > 0) {
8363
9876
  store.save();
8364
9877
  invertedIndex.save();
8365
9878
  }
8366
- return { succeeded, failed, remaining: stillFailing.length };
9879
+ if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
9880
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9881
+ this.saveIndexMetadata(configuredProviderInfo);
9882
+ this.indexCompatibility = { compatible: true };
9883
+ }
9884
+ return { succeeded, failed, remaining: persistedStillFailing.length };
8367
9885
  }
8368
9886
  getFailedBatchesCount() {
9887
+ if (this.config.scope === "global") {
9888
+ return this.partitionFailedBatches(this.getScopedRoots()).scoped.length;
9889
+ }
8369
9890
  return this.loadFailedBatches().length;
8370
9891
  }
8371
9892
  getCurrentBranch() {
@@ -8414,20 +9935,23 @@ var Indexer = class {
8414
9935
  const semanticResults = store.search(embedding, limit * 2);
8415
9936
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
8416
9937
  let branchChunkIds = null;
8417
- if (filterByBranch && this.currentBranch !== "default") {
8418
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9938
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9939
+ branchChunkIds = new Set(
9940
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9941
+ );
8419
9942
  }
8420
9943
  const prefilterStartTime = import_perf_hooks.performance.now();
8421
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9944
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9945
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8422
9946
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8423
- const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9947
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
8424
9948
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
8425
- if (branchChunkIds && branchChunkIds.size === 0) {
9949
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8426
9950
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8427
9951
  branch: this.currentBranch
8428
9952
  });
8429
9953
  }
8430
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9954
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8431
9955
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8432
9956
  branch: this.currentBranch
8433
9957
  });
@@ -8476,7 +10000,7 @@ var Indexer = class {
8476
10000
  let content = "";
8477
10001
  if (this.config.search.includeContext) {
8478
10002
  try {
8479
- const fileContent = await import_fs5.promises.readFile(
10003
+ const fileContent = await import_fs6.promises.readFile(
8480
10004
  r.metadata.filePath,
8481
10005
  "utf-8"
8482
10006
  );
@@ -8500,11 +10024,39 @@ var Indexer = class {
8500
10024
  }
8501
10025
  async getCallers(targetName) {
8502
10026
  const { database } = await this.ensureInitialized();
8503
- return database.getCallersWithContext(targetName, this.currentBranch);
10027
+ const seen = /* @__PURE__ */ new Set();
10028
+ const results = [];
10029
+ for (const branchKey of this.getBranchCatalogKeys()) {
10030
+ for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10031
+ if (!seen.has(edge.id)) {
10032
+ seen.add(edge.id);
10033
+ results.push(edge);
10034
+ }
10035
+ }
10036
+ }
10037
+ return results;
8504
10038
  }
8505
10039
  async getCallees(symbolId) {
8506
10040
  const { database } = await this.ensureInitialized();
8507
- return database.getCallees(symbolId, this.currentBranch);
10041
+ const seen = /* @__PURE__ */ new Set();
10042
+ const results = [];
10043
+ for (const branchKey of this.getBranchCatalogKeys()) {
10044
+ for (const edge of database.getCallees(symbolId, branchKey)) {
10045
+ if (!seen.has(edge.id)) {
10046
+ seen.add(edge.id);
10047
+ results.push(edge);
10048
+ }
10049
+ }
10050
+ }
10051
+ return results;
10052
+ }
10053
+ async close() {
10054
+ await this.database?.close();
10055
+ this.database = null;
10056
+ this.store = null;
10057
+ this.invertedIndex = null;
10058
+ this.provider = null;
10059
+ this.reranker = null;
8508
10060
  }
8509
10061
  };
8510
10062
 
@@ -8517,7 +10069,17 @@ function truncateContent(content) {
8517
10069
  // ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
8518
10070
  }
8519
10071
  function formatIndexStats(stats, verbose = false) {
10072
+ if (stats.resetCorruptedIndex) {
10073
+ return stats.warning ?? "Detected a corrupted local index and reset it during indexing. Run index_codebase again to rebuild search data.";
10074
+ }
8520
10075
  const lines = [];
10076
+ if (stats.failedChunks > 0) {
10077
+ lines.push(`INDEXING WARNING: ${stats.failedChunks} chunks failed to embed.`);
10078
+ if (stats.failedBatchesPath) {
10079
+ lines.push(`Inspect failed batches at: ${stats.failedBatchesPath}`);
10080
+ }
10081
+ lines.push("");
10082
+ }
8521
10083
  if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
8522
10084
  lines.push(`${stats.totalFiles} files processed, ${stats.existingChunks} code chunks already up to date.`);
8523
10085
  } else if (stats.indexedChunks === 0) {
@@ -8562,6 +10124,19 @@ function formatIndexStats(stats, verbose = false) {
8562
10124
  }
8563
10125
  function formatStatus(status) {
8564
10126
  if (!status.indexed) {
10127
+ if (status.warning) {
10128
+ return status.warning;
10129
+ }
10130
+ if (status.failedBatchesCount > 0) {
10131
+ const lines2 = [
10132
+ "Codebase is not indexed. The last indexing run left failed embedding batches.",
10133
+ "Fix the provider/model configuration, then rerun index_codebase normally to retry the saved failed batches. Use force=true only for a full rebuild or compatibility reset."
10134
+ ];
10135
+ if (status.failedBatchesPath) {
10136
+ lines2.push(`Failed batches: ${status.failedBatchesPath}`);
10137
+ }
10138
+ return lines2.join("\n");
10139
+ }
8565
10140
  return "Codebase is not indexed. Run index_codebase to create an index.";
8566
10141
  }
8567
10142
  const lines = [
@@ -8574,6 +10149,13 @@ function formatStatus(status) {
8574
10149
  lines.push(`Current branch: ${status.currentBranch}`);
8575
10150
  lines.push(`Base branch: ${status.baseBranch}`);
8576
10151
  }
10152
+ if (status.failedBatchesCount > 0) {
10153
+ lines.push("");
10154
+ lines.push(`INDEXING WARNING: ${status.failedBatchesCount} failed embedding batch${status.failedBatchesCount === 1 ? " remains" : "es remain"}.`);
10155
+ if (status.failedBatchesPath) {
10156
+ lines.push(`Failed batches: ${status.failedBatchesPath}`);
10157
+ }
10158
+ }
8577
10159
  if (status.compatibility && !status.compatibility.compatible) {
8578
10160
  lines.push("");
8579
10161
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8631,6 +10213,9 @@ function formatCodebasePeek(results) {
8631
10213
  return formatted.join("\n");
8632
10214
  }
8633
10215
  function formatHealthCheck(result) {
10216
+ if (result.resetCorruptedIndex) {
10217
+ return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
10218
+ }
8634
10219
  if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
8635
10220
  return "Index is healthy. No stale entries found.";
8636
10221
  }
@@ -8690,8 +10275,8 @@ ${truncateContent(r.content)}
8690
10275
  }
8691
10276
 
8692
10277
  // src/tools/index.ts
8693
- var import_fs6 = require("fs");
8694
- var path8 = __toESM(require("path"), 1);
10278
+ var import_fs7 = require("fs");
10279
+ var path9 = __toESM(require("path"), 1);
8695
10280
  var z = import_plugin.tool.schema;
8696
10281
  var sharedIndexer = null;
8697
10282
  var sharedProjectRoot = "";
@@ -8706,7 +10291,20 @@ function refreshIndexerFromConfig() {
8706
10291
  if (!sharedProjectRoot) {
8707
10292
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
8708
10293
  }
8709
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadConfig()));
10294
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10295
+ }
10296
+ function shouldForceLocalizeProjectIndex() {
10297
+ const currentConfig = parseConfig(loadRuntimeConfig());
10298
+ if (currentConfig.scope !== "project") {
10299
+ return false;
10300
+ }
10301
+ const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10302
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10303
+ if (!mainRepoRoot) {
10304
+ return false;
10305
+ }
10306
+ const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10307
+ return !(0, import_fs7.existsSync)(localIndexPath) && (0, import_fs7.existsSync)(inheritedIndexPath);
8710
10308
  }
8711
10309
  function getIndexer() {
8712
10310
  if (!sharedIndexer) {
@@ -8715,9 +10313,41 @@ function getIndexer() {
8715
10313
  return sharedIndexer;
8716
10314
  }
8717
10315
  function getConfigPath() {
8718
- return path8.join(sharedProjectRoot, ".opencode", "codebase-index.json");
10316
+ return resolveWritableProjectConfigPath(sharedProjectRoot);
8719
10317
  }
8720
- function loadConfig() {
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() {
8721
10351
  const rawConfig = loadMergedConfig(sharedProjectRoot);
8722
10352
  const config = {};
8723
10353
  if (rawConfig && typeof rawConfig === "object") {
@@ -8725,27 +10355,32 @@ function loadConfig() {
8725
10355
  config[key] = rawConfig[key];
8726
10356
  }
8727
10357
  }
8728
- if (Array.isArray(config.knowledgeBases)) {
8729
- config.knowledgeBases = config.knowledgeBases.map((kb) => {
8730
- const resolved = path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb);
8731
- return path8.normalize(resolved);
8732
- });
8733
- }
8734
- if (Array.isArray(config.additionalInclude)) {
8735
- config.additionalInclude = config.additionalInclude.map((pattern) => {
8736
- const resolved = path8.isAbsolute(pattern) ? pattern : path8.resolve(sharedProjectRoot, pattern);
8737
- return path8.normalize(resolved);
8738
- });
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
+ }
8739
10367
  }
8740
- return config;
10368
+ return normalizeKnowledgeBasePaths(config);
8741
10369
  }
8742
10370
  function saveConfig(config) {
8743
- const configDir = path8.join(sharedProjectRoot, ".opencode");
8744
- if (!(0, import_fs6.existsSync)(configDir)) {
8745
- (0, import_fs6.mkdirSync)(configDir, { recursive: true });
8746
- }
8747
10371
  const configPath = getConfigPath();
8748
- (0, import_fs6.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
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");
8749
10384
  }
8750
10385
  var codebase_peek = (0, import_plugin.tool)({
8751
10386
  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.",
@@ -8775,12 +10410,17 @@ var index_codebase = (0, import_plugin.tool)({
8775
10410
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
8776
10411
  },
8777
10412
  async execute(args, context) {
8778
- const indexer = getIndexer();
10413
+ let indexer = getIndexer();
8779
10414
  if (args.estimateOnly) {
8780
10415
  const estimate = await indexer.estimateCost();
8781
10416
  return formatCostEstimate(estimate);
8782
10417
  }
8783
10418
  if (args.force) {
10419
+ if (shouldForceLocalizeProjectIndex()) {
10420
+ materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10421
+ refreshIndexerFromConfig();
10422
+ indexer = getIndexer();
10423
+ }
8784
10424
  await indexer.clearIndex();
8785
10425
  }
8786
10426
  const stats = await indexer.index((progress) => {
@@ -8961,23 +10601,23 @@ var add_knowledge_base = (0, import_plugin.tool)({
8961
10601
  },
8962
10602
  async execute(args) {
8963
10603
  const inputPath = args.path.trim();
8964
- const resolvedPath = path8.isAbsolute(inputPath) ? inputPath : path8.resolve(sharedProjectRoot, inputPath);
8965
- if (!(0, import_fs6.existsSync)(resolvedPath)) {
10604
+ const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10605
+ if (!(0, import_fs7.existsSync)(resolvedPath)) {
8966
10606
  return `Error: Directory does not exist: ${resolvedPath}`;
8967
10607
  }
8968
10608
  try {
8969
- const stat4 = (0, import_fs6.statSync)(resolvedPath);
10609
+ const stat4 = (0, import_fs7.statSync)(resolvedPath);
8970
10610
  if (!stat4.isDirectory()) {
8971
10611
  return `Error: Path is not a directory: ${resolvedPath}`;
8972
10612
  }
8973
10613
  } catch (error) {
8974
10614
  return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
8975
10615
  }
8976
- const config = loadConfig();
10616
+ const config = loadEditableConfig();
8977
10617
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
8978
- const normalizedPath = path8.normalize(resolvedPath);
10618
+ const normalizedPath = path9.normalize(resolvedPath);
8979
10619
  const alreadyExists = knowledgeBases.some(
8980
- (kb) => path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedPath
10620
+ (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
8981
10621
  );
8982
10622
  if (alreadyExists) {
8983
10623
  return `Knowledge base already configured: ${resolvedPath}`;
@@ -9001,7 +10641,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
9001
10641
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
9002
10642
  args: {},
9003
10643
  async execute() {
9004
- const config = loadConfig();
10644
+ const config = loadRuntimeConfig();
9005
10645
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9006
10646
  if (knowledgeBases.length === 0) {
9007
10647
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -9011,8 +10651,8 @@ var list_knowledge_bases = (0, import_plugin.tool)({
9011
10651
  `;
9012
10652
  for (let i = 0; i < knowledgeBases.length; i++) {
9013
10653
  const kb = knowledgeBases[i];
9014
- const resolvedPath = path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb);
9015
- const exists = (0, import_fs6.existsSync)(resolvedPath);
10654
+ const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10655
+ const exists = (0, import_fs7.existsSync)(resolvedPath);
9016
10656
  result += `[${i + 1}] ${kb}
9017
10657
  `;
9018
10658
  result += ` Resolved: ${resolvedPath}
@@ -9021,7 +10661,7 @@ var list_knowledge_bases = (0, import_plugin.tool)({
9021
10661
  `;
9022
10662
  if (exists) {
9023
10663
  try {
9024
- const stat4 = (0, import_fs6.statSync)(resolvedPath);
10664
+ const stat4 = (0, import_fs7.statSync)(resolvedPath);
9025
10665
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
9026
10666
  `;
9027
10667
  } catch {
@@ -9040,14 +10680,14 @@ var remove_knowledge_base = (0, import_plugin.tool)({
9040
10680
  },
9041
10681
  async execute(args) {
9042
10682
  const inputPath = args.path.trim();
9043
- const config = loadConfig();
10683
+ const config = loadEditableConfig();
9044
10684
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9045
10685
  if (knowledgeBases.length === 0) {
9046
10686
  return "No knowledge bases configured.";
9047
10687
  }
9048
- const normalizedInput = path8.normalize(inputPath);
10688
+ const normalizedInput = path9.normalize(inputPath);
9049
10689
  const index = knowledgeBases.findIndex(
9050
- (kb) => path8.normalize(kb) === normalizedInput || path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedInput
10690
+ (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
9051
10691
  );
9052
10692
  if (index === -1) {
9053
10693
  let result2 = `Knowledge base not found: ${inputPath}
@@ -9079,8 +10719,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
9079
10719
  });
9080
10720
 
9081
10721
  // src/commands/loader.ts
9082
- var import_fs7 = require("fs");
9083
- var path9 = __toESM(require("path"), 1);
10722
+ var import_fs8 = require("fs");
10723
+ var path10 = __toESM(require("path"), 1);
9084
10724
  function parseFrontmatter(content) {
9085
10725
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
9086
10726
  const match = content.match(frontmatterRegex);
@@ -9101,15 +10741,15 @@ function parseFrontmatter(content) {
9101
10741
  }
9102
10742
  function loadCommandsFromDirectory(commandsDir) {
9103
10743
  const commands = /* @__PURE__ */ new Map();
9104
- if (!(0, import_fs7.existsSync)(commandsDir)) {
10744
+ if (!(0, import_fs8.existsSync)(commandsDir)) {
9105
10745
  return commands;
9106
10746
  }
9107
- const files = (0, import_fs7.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
10747
+ const files = (0, import_fs8.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
9108
10748
  for (const file of files) {
9109
- const filePath = path9.join(commandsDir, file);
9110
- const content = (0, import_fs7.readFileSync)(filePath, "utf-8");
10749
+ const filePath = path10.join(commandsDir, file);
10750
+ const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
9111
10751
  const { frontmatter, body } = parseFrontmatter(content);
9112
- const name = path9.basename(file, ".md");
10752
+ const name = path10.basename(file, ".md");
9113
10753
  const description = frontmatter.description || `Run the ${name} command`;
9114
10754
  commands.set(name, {
9115
10755
  description,
@@ -9397,9 +11037,9 @@ function replaceActiveWatcher(nextWatcher) {
9397
11037
  function getCommandsDir() {
9398
11038
  let currentDir = process.cwd();
9399
11039
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
9400
- currentDir = path10.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
11040
+ currentDir = path11.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
9401
11041
  }
9402
- return path10.join(currentDir, "..", "commands");
11042
+ return path11.join(currentDir, "..", "commands");
9403
11043
  }
9404
11044
  var plugin = async ({ directory }) => {
9405
11045
  try {