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.js CHANGED
@@ -324,7 +324,7 @@ var require_ignore = __commonJS({
324
324
  // path matching.
325
325
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
326
326
  // @returns {TestResult} true if a file is ignored
327
- test(path11, checkUnignored, mode) {
327
+ test(path12, checkUnignored, mode) {
328
328
  let ignored = false;
329
329
  let unignored = false;
330
330
  let matchedRule;
@@ -333,7 +333,7 @@ var require_ignore = __commonJS({
333
333
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
334
334
  return;
335
335
  }
336
- const matched = rule[mode].test(path11);
336
+ const matched = rule[mode].test(path12);
337
337
  if (!matched) {
338
338
  return;
339
339
  }
@@ -354,17 +354,17 @@ var require_ignore = __commonJS({
354
354
  var throwError = (message, Ctor) => {
355
355
  throw new Ctor(message);
356
356
  };
357
- var checkPath = (path11, originalPath, doThrow) => {
358
- if (!isString(path11)) {
357
+ var checkPath = (path12, originalPath, doThrow) => {
358
+ if (!isString(path12)) {
359
359
  return doThrow(
360
360
  `path must be a string, but got \`${originalPath}\``,
361
361
  TypeError
362
362
  );
363
363
  }
364
- if (!path11) {
364
+ if (!path12) {
365
365
  return doThrow(`path must not be empty`, TypeError);
366
366
  }
367
- if (checkPath.isNotRelative(path11)) {
367
+ if (checkPath.isNotRelative(path12)) {
368
368
  const r = "`path.relative()`d";
369
369
  return doThrow(
370
370
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -373,7 +373,7 @@ var require_ignore = __commonJS({
373
373
  }
374
374
  return true;
375
375
  };
376
- var isNotRelative = (path11) => REGEX_TEST_INVALID_PATH.test(path11);
376
+ var isNotRelative = (path12) => REGEX_TEST_INVALID_PATH.test(path12);
377
377
  checkPath.isNotRelative = isNotRelative;
378
378
  checkPath.convert = (p) => p;
379
379
  var Ignore2 = class {
@@ -403,19 +403,19 @@ var require_ignore = __commonJS({
403
403
  }
404
404
  // @returns {TestResult}
405
405
  _test(originalPath, cache, checkUnignored, slices) {
406
- const path11 = originalPath && checkPath.convert(originalPath);
406
+ const path12 = originalPath && checkPath.convert(originalPath);
407
407
  checkPath(
408
- path11,
408
+ path12,
409
409
  originalPath,
410
410
  this._strictPathCheck ? throwError : RETURN_FALSE
411
411
  );
412
- return this._t(path11, cache, checkUnignored, slices);
412
+ return this._t(path12, cache, checkUnignored, slices);
413
413
  }
414
- checkIgnore(path11) {
415
- if (!REGEX_TEST_TRAILING_SLASH.test(path11)) {
416
- return this.test(path11);
414
+ checkIgnore(path12) {
415
+ if (!REGEX_TEST_TRAILING_SLASH.test(path12)) {
416
+ return this.test(path12);
417
417
  }
418
- const slices = path11.split(SLASH2).filter(Boolean);
418
+ const slices = path12.split(SLASH2).filter(Boolean);
419
419
  slices.pop();
420
420
  if (slices.length) {
421
421
  const parent = this._t(
@@ -428,18 +428,18 @@ var require_ignore = __commonJS({
428
428
  return parent;
429
429
  }
430
430
  }
431
- return this._rules.test(path11, false, MODE_CHECK_IGNORE);
431
+ return this._rules.test(path12, false, MODE_CHECK_IGNORE);
432
432
  }
433
- _t(path11, cache, checkUnignored, slices) {
434
- if (path11 in cache) {
435
- return cache[path11];
433
+ _t(path12, cache, checkUnignored, slices) {
434
+ if (path12 in cache) {
435
+ return cache[path12];
436
436
  }
437
437
  if (!slices) {
438
- slices = path11.split(SLASH2).filter(Boolean);
438
+ slices = path12.split(SLASH2).filter(Boolean);
439
439
  }
440
440
  slices.pop();
441
441
  if (!slices.length) {
442
- return cache[path11] = this._rules.test(path11, checkUnignored, MODE_IGNORE);
442
+ return cache[path12] = this._rules.test(path12, checkUnignored, MODE_IGNORE);
443
443
  }
444
444
  const parent = this._t(
445
445
  slices.join(SLASH2) + SLASH2,
@@ -447,29 +447,29 @@ var require_ignore = __commonJS({
447
447
  checkUnignored,
448
448
  slices
449
449
  );
450
- return cache[path11] = parent.ignored ? parent : this._rules.test(path11, checkUnignored, MODE_IGNORE);
450
+ return cache[path12] = parent.ignored ? parent : this._rules.test(path12, checkUnignored, MODE_IGNORE);
451
451
  }
452
- ignores(path11) {
453
- return this._test(path11, this._ignoreCache, false).ignored;
452
+ ignores(path12) {
453
+ return this._test(path12, this._ignoreCache, false).ignored;
454
454
  }
455
455
  createFilter() {
456
- return (path11) => !this.ignores(path11);
456
+ return (path12) => !this.ignores(path12);
457
457
  }
458
458
  filter(paths) {
459
459
  return makeArray(paths).filter(this.createFilter());
460
460
  }
461
461
  // @returns {TestResult}
462
- test(path11) {
463
- return this._test(path11, this._testCache, true);
462
+ test(path12) {
463
+ return this._test(path12, this._testCache, true);
464
464
  }
465
465
  };
466
466
  var factory = (options) => new Ignore2(options);
467
- var isPathValid = (path11) => checkPath(path11 && checkPath.convert(path11), path11, RETURN_FALSE);
467
+ var isPathValid = (path12) => checkPath(path12 && checkPath.convert(path12), path12, RETURN_FALSE);
468
468
  var setupWindows = () => {
469
469
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
470
470
  checkPath.convert = makePosix;
471
471
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
472
- checkPath.isNotRelative = (path11) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path11) || isNotRelative(path11);
472
+ checkPath.isNotRelative = (path12) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path12) || isNotRelative(path12);
473
473
  };
474
474
  if (
475
475
  // Detect `process` so that it can run in browsers.
@@ -647,7 +647,7 @@ var require_eventemitter3 = __commonJS({
647
647
  });
648
648
 
649
649
  // src/index.ts
650
- import * as path10 from "path";
650
+ import * as path11 from "path";
651
651
  import { fileURLToPath as fileURLToPath2 } from "url";
652
652
 
653
653
  // src/config/constants.ts
@@ -657,12 +657,14 @@ var DEFAULT_INCLUDE = [
657
657
  "**/*.{go,rs,java,kt,scala}",
658
658
  "**/*.{c,cpp,cc,h,hpp}",
659
659
  "**/*.{rb,php,inc,swift}",
660
+ "**/*.{cls,trigger}",
660
661
  "**/*.{vue,svelte,astro}",
661
662
  "**/*.{sql,graphql,proto}",
662
663
  "**/*.{yaml,yml,toml}",
663
664
  "**/*.{md,mdx}",
664
665
  "**/*.{sh,bash,zsh}",
665
- "**/*.{txt,html,htm}"
666
+ "**/*.{txt,html,htm}",
667
+ "**/*.zig"
666
668
  ];
667
669
  var DEFAULT_EXCLUDE = [
668
670
  "**/node_modules/**",
@@ -727,7 +729,7 @@ var EMBEDDING_MODELS = {
727
729
  provider: "ollama",
728
730
  model: "nomic-embed-text",
729
731
  dimensions: 768,
730
- maxTokens: 8192,
732
+ maxTokens: 2048,
731
733
  costPer1MTokens: 0
732
734
  },
733
735
  "mxbai-embed-large": {
@@ -751,9 +753,15 @@ var EMBEDDING_MODELS = {
751
753
  var DEFAULT_PROVIDER_MODELS = {
752
754
  "github-copilot": "text-embedding-3-small",
753
755
  "openai": "text-embedding-3-small",
754
- "google": "text-embedding-005",
756
+ "google": "gemini-embedding-001",
755
757
  "ollama": "nomic-embed-text"
756
758
  };
759
+ var AUTO_DETECT_PROVIDER_ORDER = [
760
+ "ollama",
761
+ "github-copilot",
762
+ "openai",
763
+ "google"
764
+ ];
757
765
 
758
766
  // src/config/env-substitution.ts
759
767
  var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
@@ -1012,26 +1020,261 @@ function getDefaultModelForProvider(provider) {
1012
1020
  return models[providerDefault];
1013
1021
  }
1014
1022
  var availableProviders = Object.keys(EMBEDDING_MODELS);
1023
+ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1024
+ (provider) => provider in EMBEDDING_MODELS
1025
+ );
1015
1026
 
1016
1027
  // src/config/merger.ts
1017
- import { existsSync, readFileSync } from "fs";
1018
- import * as path from "path";
1028
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1029
+ import * as os2 from "os";
1030
+ import * as path3 from "path";
1031
+
1032
+ // src/config/paths.ts
1033
+ import { existsSync as existsSync2 } from "fs";
1019
1034
  import * as os from "os";
1035
+ import * as path2 from "path";
1036
+
1037
+ // src/git/index.ts
1038
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
1039
+ import * as path from "path";
1040
+ function readPackedRefs(gitDir) {
1041
+ const packedRefsPath = path.join(gitDir, "packed-refs");
1042
+ if (!existsSync(packedRefsPath)) {
1043
+ return [];
1044
+ }
1045
+ try {
1046
+ return readFileSync(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
1047
+ } catch {
1048
+ return [];
1049
+ }
1050
+ }
1051
+ function resolveCommonGitDir(gitDir) {
1052
+ const commonDirPath = path.join(gitDir, "commondir");
1053
+ if (!existsSync(commonDirPath)) {
1054
+ return gitDir;
1055
+ }
1056
+ try {
1057
+ const raw = readFileSync(commonDirPath, "utf-8").trim();
1058
+ if (!raw) {
1059
+ return gitDir;
1060
+ }
1061
+ const resolved = path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
1062
+ if (existsSync(resolved)) {
1063
+ return resolved;
1064
+ }
1065
+ } catch {
1066
+ return gitDir;
1067
+ }
1068
+ return gitDir;
1069
+ }
1070
+ function resolveWorktreeMainRepoRoot(repoRoot) {
1071
+ const gitDir = resolveGitDir(repoRoot);
1072
+ if (!gitDir) {
1073
+ return null;
1074
+ }
1075
+ const commonGitDir = resolveCommonGitDir(gitDir);
1076
+ if (commonGitDir === gitDir || path.basename(commonGitDir) !== ".git") {
1077
+ return null;
1078
+ }
1079
+ const mainRepoRoot = path.dirname(commonGitDir);
1080
+ if (!existsSync(mainRepoRoot)) {
1081
+ return null;
1082
+ }
1083
+ return path.resolve(mainRepoRoot) === path.resolve(repoRoot) ? null : mainRepoRoot;
1084
+ }
1085
+ function resolveGitDir(repoRoot) {
1086
+ const gitPath = path.join(repoRoot, ".git");
1087
+ if (!existsSync(gitPath)) {
1088
+ return null;
1089
+ }
1090
+ try {
1091
+ const stat4 = statSync(gitPath);
1092
+ if (stat4.isDirectory()) {
1093
+ return gitPath;
1094
+ }
1095
+ if (stat4.isFile()) {
1096
+ const content = readFileSync(gitPath, "utf-8").trim();
1097
+ const match = content.match(/^gitdir:\s*(.+)$/);
1098
+ if (match) {
1099
+ const gitdir = match[1];
1100
+ const resolvedPath = path.isAbsolute(gitdir) ? gitdir : path.resolve(repoRoot, gitdir);
1101
+ if (existsSync(resolvedPath)) {
1102
+ return resolvedPath;
1103
+ }
1104
+ }
1105
+ }
1106
+ } catch {
1107
+ }
1108
+ return null;
1109
+ }
1110
+ function isGitRepo(dir) {
1111
+ return resolveGitDir(dir) !== null;
1112
+ }
1113
+ function getCurrentBranch(repoRoot) {
1114
+ const gitDir = resolveGitDir(repoRoot);
1115
+ if (!gitDir) {
1116
+ return null;
1117
+ }
1118
+ const headPath = path.join(gitDir, "HEAD");
1119
+ if (!existsSync(headPath)) {
1120
+ return null;
1121
+ }
1122
+ try {
1123
+ const headContent = readFileSync(headPath, "utf-8").trim();
1124
+ const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
1125
+ if (match) {
1126
+ return match[1];
1127
+ }
1128
+ if (/^[0-9a-f]{40}$/i.test(headContent)) {
1129
+ return headContent.slice(0, 7);
1130
+ }
1131
+ return null;
1132
+ } catch {
1133
+ return null;
1134
+ }
1135
+ }
1136
+ function getBaseBranch(repoRoot) {
1137
+ const gitDir = resolveGitDir(repoRoot);
1138
+ const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
1139
+ const candidates = ["main", "master", "develop", "trunk"];
1140
+ if (refStoreDir) {
1141
+ for (const candidate of candidates) {
1142
+ const refPath = path.join(refStoreDir, "refs", "heads", candidate);
1143
+ if (existsSync(refPath)) {
1144
+ return candidate;
1145
+ }
1146
+ const packedRefs = readPackedRefs(refStoreDir);
1147
+ if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
1148
+ return candidate;
1149
+ }
1150
+ }
1151
+ }
1152
+ return getCurrentBranch(repoRoot) ?? "main";
1153
+ }
1154
+ function getBranchOrDefault(repoRoot) {
1155
+ if (!isGitRepo(repoRoot)) {
1156
+ return "default";
1157
+ }
1158
+ return getCurrentBranch(repoRoot) ?? "default";
1159
+ }
1160
+ function getHeadPath(repoRoot) {
1161
+ const gitDir = resolveGitDir(repoRoot);
1162
+ if (gitDir) {
1163
+ return path.join(gitDir, "HEAD");
1164
+ }
1165
+ return path.join(repoRoot, ".git", "HEAD");
1166
+ }
1167
+
1168
+ // src/config/paths.ts
1169
+ var PROJECT_CONFIG_RELATIVE_PATH = path2.join(".opencode", "codebase-index.json");
1170
+ var PROJECT_INDEX_RELATIVE_PATH = path2.join(".opencode", "index");
1171
+ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1172
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
1173
+ if (!mainRepoRoot) {
1174
+ return null;
1175
+ }
1176
+ const fallbackPath = path2.join(mainRepoRoot, relativePath);
1177
+ return existsSync2(fallbackPath) ? fallbackPath : null;
1178
+ }
1179
+ function hasProjectConfig(projectRoot) {
1180
+ return existsSync2(path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH));
1181
+ }
1182
+ function getGlobalIndexPath() {
1183
+ return path2.join(os.homedir(), ".opencode", "global-index");
1184
+ }
1185
+ function resolveProjectConfigPath(projectRoot) {
1186
+ const localConfigPath = path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1187
+ if (existsSync2(localConfigPath)) {
1188
+ return localConfigPath;
1189
+ }
1190
+ return resolveWorktreeFallbackPath(projectRoot, PROJECT_CONFIG_RELATIVE_PATH) ?? localConfigPath;
1191
+ }
1192
+ function resolveWritableProjectConfigPath(projectRoot) {
1193
+ return path2.join(projectRoot, PROJECT_CONFIG_RELATIVE_PATH);
1194
+ }
1195
+ function resolveProjectIndexPath(projectRoot, scope) {
1196
+ if (scope === "global") {
1197
+ return getGlobalIndexPath();
1198
+ }
1199
+ const localIndexPath = path2.join(projectRoot, PROJECT_INDEX_RELATIVE_PATH);
1200
+ if (existsSync2(localIndexPath)) {
1201
+ return localIndexPath;
1202
+ }
1203
+ if (hasProjectConfig(projectRoot)) {
1204
+ return localIndexPath;
1205
+ }
1206
+ return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
1207
+ }
1208
+
1209
+ // src/config/merger.ts
1020
1210
  function loadJsonFile(filePath) {
1021
1211
  try {
1022
- if (existsSync(filePath)) {
1023
- const content = readFileSync(filePath, "utf-8");
1212
+ if (existsSync3(filePath)) {
1213
+ const content = readFileSync2(filePath, "utf-8");
1024
1214
  return JSON.parse(content);
1025
1215
  }
1026
1216
  } catch {
1027
1217
  }
1028
1218
  return null;
1029
1219
  }
1220
+ function normalizeRelativeConfigPath(candidate) {
1221
+ return candidate.replace(/\\/g, "/");
1222
+ }
1223
+ function isWithinRoot(rootDir, targetPath) {
1224
+ const relativePath = path3.relative(rootDir, targetPath);
1225
+ return relativePath === "" || !relativePath.startsWith("..") && !path3.isAbsolute(relativePath);
1226
+ }
1227
+ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1228
+ if (!Array.isArray(values)) {
1229
+ return [];
1230
+ }
1231
+ return values.filter((value) => typeof value === "string").map((value) => {
1232
+ const trimmed = value.trim();
1233
+ if (!trimmed) {
1234
+ return trimmed;
1235
+ }
1236
+ if (path3.isAbsolute(trimmed)) {
1237
+ if (isWithinRoot(sourceRoot, trimmed)) {
1238
+ return normalizeRelativeConfigPath(path3.normalize(path3.relative(sourceRoot, trimmed) || "."));
1239
+ }
1240
+ return path3.normalize(trimmed);
1241
+ }
1242
+ const resolvedFromSource = path3.resolve(sourceRoot, trimmed);
1243
+ if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1244
+ return normalizeRelativeConfigPath(path3.normalize(trimmed));
1245
+ }
1246
+ return normalizeRelativeConfigPath(path3.normalize(path3.relative(targetRoot, resolvedFromSource)));
1247
+ }).filter(Boolean);
1248
+ }
1249
+ function materializeLocalProjectConfig(projectRoot, config) {
1250
+ const localConfigPath = path3.join(projectRoot, ".opencode", "codebase-index.json");
1251
+ mkdirSync(path3.dirname(localConfigPath), { recursive: true });
1252
+ writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1253
+ return localConfigPath;
1254
+ }
1255
+ function loadProjectConfigLayer(projectRoot) {
1256
+ const projectConfigPath = resolveProjectConfigPath(projectRoot);
1257
+ const projectConfig = loadJsonFile(projectConfigPath);
1258
+ if (!projectConfig) {
1259
+ return {};
1260
+ }
1261
+ const normalizedConfig = { ...projectConfig };
1262
+ const projectConfigBaseDir = path3.dirname(path3.dirname(projectConfigPath));
1263
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
1264
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1265
+ normalizedConfig.knowledgeBases,
1266
+ projectConfigBaseDir,
1267
+ projectRoot
1268
+ );
1269
+ }
1270
+ return normalizedConfig;
1271
+ }
1030
1272
  function loadMergedConfig(projectRoot) {
1031
- const globalConfigPath = path.join(os.homedir(), ".config", "opencode", "codebase-index.json");
1273
+ const globalConfigPath = os2.homedir() + "/.config/opencode/codebase-index.json";
1032
1274
  const globalConfig = loadJsonFile(globalConfigPath);
1033
- const projectConfigPath = path.join(projectRoot, ".opencode", "codebase-index.json");
1275
+ const projectConfigPath = resolveProjectConfigPath(projectRoot);
1034
1276
  const projectConfig = loadJsonFile(projectConfigPath);
1277
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot);
1035
1278
  if (!globalConfig && !projectConfig) {
1036
1279
  return {};
1037
1280
  }
@@ -1039,56 +1282,56 @@ function loadMergedConfig(projectRoot) {
1039
1282
  return globalConfig;
1040
1283
  }
1041
1284
  if (!globalConfig && projectConfig) {
1042
- return projectConfig;
1285
+ return normalizedProjectConfig;
1043
1286
  }
1044
1287
  const merged = { ...globalConfig };
1045
- if (projectConfig && "embeddingProvider" in projectConfig) {
1046
- merged.embeddingProvider = projectConfig.embeddingProvider;
1288
+ if (projectConfig && "embeddingProvider" in normalizedProjectConfig) {
1289
+ merged.embeddingProvider = normalizedProjectConfig.embeddingProvider;
1047
1290
  } else if (globalConfig && globalConfig.embeddingProvider) {
1048
1291
  merged.embeddingProvider = globalConfig.embeddingProvider;
1049
1292
  }
1050
- if (projectConfig && "customProvider" in projectConfig) {
1051
- merged.customProvider = projectConfig.customProvider;
1293
+ if (projectConfig && "customProvider" in normalizedProjectConfig) {
1294
+ merged.customProvider = normalizedProjectConfig.customProvider;
1052
1295
  } else if (globalConfig && globalConfig.customProvider) {
1053
1296
  merged.customProvider = globalConfig.customProvider;
1054
1297
  }
1055
- if (projectConfig && "embeddingModel" in projectConfig) {
1056
- merged.embeddingModel = projectConfig.embeddingModel;
1298
+ if (projectConfig && "embeddingModel" in normalizedProjectConfig) {
1299
+ merged.embeddingModel = normalizedProjectConfig.embeddingModel;
1057
1300
  } else if (globalConfig && globalConfig.embeddingModel) {
1058
1301
  merged.embeddingModel = globalConfig.embeddingModel;
1059
1302
  }
1060
- if (projectConfig && "reranker" in projectConfig) {
1061
- merged.reranker = projectConfig.reranker;
1303
+ if (projectConfig && "reranker" in normalizedProjectConfig) {
1304
+ merged.reranker = normalizedProjectConfig.reranker;
1062
1305
  } else if (globalConfig && globalConfig.reranker) {
1063
1306
  merged.reranker = globalConfig.reranker;
1064
1307
  }
1065
- if (projectConfig && "include" in projectConfig) {
1066
- merged.include = projectConfig.include;
1308
+ if (projectConfig && "include" in normalizedProjectConfig) {
1309
+ merged.include = normalizedProjectConfig.include;
1067
1310
  } else if (globalConfig && globalConfig.include) {
1068
1311
  merged.include = globalConfig.include;
1069
1312
  }
1070
- if (projectConfig && "exclude" in projectConfig) {
1071
- merged.exclude = projectConfig.exclude;
1313
+ if (projectConfig && "exclude" in normalizedProjectConfig) {
1314
+ merged.exclude = normalizedProjectConfig.exclude;
1072
1315
  } else if (globalConfig && globalConfig.exclude) {
1073
1316
  merged.exclude = globalConfig.exclude;
1074
1317
  }
1075
- if (projectConfig && "indexing" in projectConfig) {
1076
- merged.indexing = projectConfig.indexing;
1318
+ if (projectConfig && "indexing" in normalizedProjectConfig) {
1319
+ merged.indexing = normalizedProjectConfig.indexing;
1077
1320
  } else if (globalConfig && globalConfig.indexing) {
1078
1321
  merged.indexing = globalConfig.indexing;
1079
1322
  }
1080
- if (projectConfig && "search" in projectConfig) {
1081
- merged.search = projectConfig.search;
1323
+ if (projectConfig && "search" in normalizedProjectConfig) {
1324
+ merged.search = normalizedProjectConfig.search;
1082
1325
  } else if (globalConfig && globalConfig.search) {
1083
1326
  merged.search = globalConfig.search;
1084
1327
  }
1085
- if (projectConfig && "debug" in projectConfig) {
1086
- merged.debug = projectConfig.debug;
1328
+ if (projectConfig && "debug" in normalizedProjectConfig) {
1329
+ merged.debug = normalizedProjectConfig.debug;
1087
1330
  } else if (globalConfig && globalConfig.debug) {
1088
1331
  merged.debug = globalConfig.debug;
1089
1332
  }
1090
- if (projectConfig && "scope" in projectConfig) {
1091
- merged.scope = projectConfig.scope;
1333
+ if (projectConfig && "scope" in normalizedProjectConfig) {
1334
+ merged.scope = normalizedProjectConfig.scope;
1092
1335
  } else if (globalConfig && "scope" in globalConfig) {
1093
1336
  merged.scope = globalConfig.scope;
1094
1337
  }
@@ -1097,11 +1340,11 @@ function loadMergedConfig(projectRoot) {
1097
1340
  if (key === "embeddingProvider" || key === "customProvider" || key === "embeddingModel" || key === "reranker" || key === "include" || key === "exclude" || key === "indexing" || key === "search" || key === "debug" || key === "scope" || key === "knowledgeBases" || key === "additionalInclude") {
1098
1341
  continue;
1099
1342
  }
1100
- merged[key] = projectConfig[key];
1343
+ merged[key] = normalizedProjectConfig[key];
1101
1344
  }
1102
1345
  }
1103
1346
  const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1104
- const projectKbs = projectConfig && Array.isArray(projectConfig.knowledgeBases) ? projectConfig.knowledgeBases : [];
1347
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1105
1348
  const allKbs = [...globalKbs, ...projectKbs];
1106
1349
  const uniqueKbs = [...new Set(allKbs.map((p) => String(p).trim()))];
1107
1350
  merged.knowledgeBases = uniqueKbs;
@@ -1203,7 +1446,7 @@ var ReaddirpStream = class extends Readable {
1203
1446
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
1204
1447
  const statMethod = opts.lstat ? lstat : stat;
1205
1448
  if (wantBigintFsStats) {
1206
- this._stat = (path11) => statMethod(path11, { bigint: true });
1449
+ this._stat = (path12) => statMethod(path12, { bigint: true });
1207
1450
  } else {
1208
1451
  this._stat = statMethod;
1209
1452
  }
@@ -1228,8 +1471,8 @@ var ReaddirpStream = class extends Readable {
1228
1471
  const par = this.parent;
1229
1472
  const fil = par && par.files;
1230
1473
  if (fil && fil.length > 0) {
1231
- const { path: path11, depth } = par;
1232
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path11));
1474
+ const { path: path12, depth } = par;
1475
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path12));
1233
1476
  const awaited = await Promise.all(slice);
1234
1477
  for (const entry of awaited) {
1235
1478
  if (!entry)
@@ -1269,21 +1512,21 @@ var ReaddirpStream = class extends Readable {
1269
1512
  this.reading = false;
1270
1513
  }
1271
1514
  }
1272
- async _exploreDir(path11, depth) {
1515
+ async _exploreDir(path12, depth) {
1273
1516
  let files;
1274
1517
  try {
1275
- files = await readdir(path11, this._rdOptions);
1518
+ files = await readdir(path12, this._rdOptions);
1276
1519
  } catch (error) {
1277
1520
  this._onError(error);
1278
1521
  }
1279
- return { files, depth, path: path11 };
1522
+ return { files, depth, path: path12 };
1280
1523
  }
1281
- async _formatEntry(dirent, path11) {
1524
+ async _formatEntry(dirent, path12) {
1282
1525
  let entry;
1283
- const basename4 = this._isDirent ? dirent.name : dirent;
1526
+ const basename5 = this._isDirent ? dirent.name : dirent;
1284
1527
  try {
1285
- const fullPath = presolve(pjoin(path11, basename4));
1286
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
1528
+ const fullPath = presolve(pjoin(path12, basename5));
1529
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
1287
1530
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
1288
1531
  } catch (err) {
1289
1532
  this._onError(err);
@@ -1682,16 +1925,16 @@ var delFromSet = (main, prop, item) => {
1682
1925
  };
1683
1926
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
1684
1927
  var FsWatchInstances = /* @__PURE__ */ new Map();
1685
- function createFsWatchInstance(path11, options, listener, errHandler, emitRaw) {
1928
+ function createFsWatchInstance(path12, options, listener, errHandler, emitRaw) {
1686
1929
  const handleEvent = (rawEvent, evPath) => {
1687
- listener(path11);
1688
- emitRaw(rawEvent, evPath, { watchedPath: path11 });
1689
- if (evPath && path11 !== evPath) {
1690
- fsWatchBroadcast(sp.resolve(path11, evPath), KEY_LISTENERS, sp.join(path11, evPath));
1930
+ listener(path12);
1931
+ emitRaw(rawEvent, evPath, { watchedPath: path12 });
1932
+ if (evPath && path12 !== evPath) {
1933
+ fsWatchBroadcast(sp.resolve(path12, evPath), KEY_LISTENERS, sp.join(path12, evPath));
1691
1934
  }
1692
1935
  };
1693
1936
  try {
1694
- return fs_watch(path11, {
1937
+ return fs_watch(path12, {
1695
1938
  persistent: options.persistent
1696
1939
  }, handleEvent);
1697
1940
  } catch (error) {
@@ -1707,12 +1950,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
1707
1950
  listener(val1, val2, val3);
1708
1951
  });
1709
1952
  };
1710
- var setFsWatchListener = (path11, fullPath, options, handlers) => {
1953
+ var setFsWatchListener = (path12, fullPath, options, handlers) => {
1711
1954
  const { listener, errHandler, rawEmitter } = handlers;
1712
1955
  let cont = FsWatchInstances.get(fullPath);
1713
1956
  let watcher;
1714
1957
  if (!options.persistent) {
1715
- watcher = createFsWatchInstance(path11, options, listener, errHandler, rawEmitter);
1958
+ watcher = createFsWatchInstance(path12, options, listener, errHandler, rawEmitter);
1716
1959
  if (!watcher)
1717
1960
  return;
1718
1961
  return watcher.close.bind(watcher);
@@ -1723,7 +1966,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1723
1966
  addAndConvert(cont, KEY_RAW, rawEmitter);
1724
1967
  } else {
1725
1968
  watcher = createFsWatchInstance(
1726
- path11,
1969
+ path12,
1727
1970
  options,
1728
1971
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
1729
1972
  errHandler,
@@ -1738,7 +1981,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1738
1981
  cont.watcherUnusable = true;
1739
1982
  if (isWindows && error.code === "EPERM") {
1740
1983
  try {
1741
- const fd = await open(path11, "r");
1984
+ const fd = await open(path12, "r");
1742
1985
  await fd.close();
1743
1986
  broadcastErr(error);
1744
1987
  } catch (err) {
@@ -1769,7 +2012,7 @@ var setFsWatchListener = (path11, fullPath, options, handlers) => {
1769
2012
  };
1770
2013
  };
1771
2014
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
1772
- var setFsWatchFileListener = (path11, fullPath, options, handlers) => {
2015
+ var setFsWatchFileListener = (path12, fullPath, options, handlers) => {
1773
2016
  const { listener, rawEmitter } = handlers;
1774
2017
  let cont = FsWatchFileInstances.get(fullPath);
1775
2018
  const copts = cont && cont.options;
@@ -1791,7 +2034,7 @@ var setFsWatchFileListener = (path11, fullPath, options, handlers) => {
1791
2034
  });
1792
2035
  const currmtime = curr.mtimeMs;
1793
2036
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
1794
- foreach(cont.listeners, (listener2) => listener2(path11, curr));
2037
+ foreach(cont.listeners, (listener2) => listener2(path12, curr));
1795
2038
  }
1796
2039
  })
1797
2040
  };
@@ -1821,13 +2064,13 @@ var NodeFsHandler = class {
1821
2064
  * @param listener on fs change
1822
2065
  * @returns closer for the watcher instance
1823
2066
  */
1824
- _watchWithNodeFs(path11, listener) {
2067
+ _watchWithNodeFs(path12, listener) {
1825
2068
  const opts = this.fsw.options;
1826
- const directory = sp.dirname(path11);
1827
- const basename4 = sp.basename(path11);
2069
+ const directory = sp.dirname(path12);
2070
+ const basename5 = sp.basename(path12);
1828
2071
  const parent = this.fsw._getWatchedDir(directory);
1829
- parent.add(basename4);
1830
- const absolutePath = sp.resolve(path11);
2072
+ parent.add(basename5);
2073
+ const absolutePath = sp.resolve(path12);
1831
2074
  const options = {
1832
2075
  persistent: opts.persistent
1833
2076
  };
@@ -1836,13 +2079,13 @@ var NodeFsHandler = class {
1836
2079
  let closer;
1837
2080
  if (opts.usePolling) {
1838
2081
  const enableBin = opts.interval !== opts.binaryInterval;
1839
- options.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
1840
- closer = setFsWatchFileListener(path11, absolutePath, options, {
2082
+ options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
2083
+ closer = setFsWatchFileListener(path12, absolutePath, options, {
1841
2084
  listener,
1842
2085
  rawEmitter: this.fsw._emitRaw
1843
2086
  });
1844
2087
  } else {
1845
- closer = setFsWatchListener(path11, absolutePath, options, {
2088
+ closer = setFsWatchListener(path12, absolutePath, options, {
1846
2089
  listener,
1847
2090
  errHandler: this._boundHandleError,
1848
2091
  rawEmitter: this.fsw._emitRaw
@@ -1858,13 +2101,13 @@ var NodeFsHandler = class {
1858
2101
  if (this.fsw.closed) {
1859
2102
  return;
1860
2103
  }
1861
- const dirname5 = sp.dirname(file);
1862
- const basename4 = sp.basename(file);
1863
- const parent = this.fsw._getWatchedDir(dirname5);
2104
+ const dirname8 = sp.dirname(file);
2105
+ const basename5 = sp.basename(file);
2106
+ const parent = this.fsw._getWatchedDir(dirname8);
1864
2107
  let prevStats = stats;
1865
- if (parent.has(basename4))
2108
+ if (parent.has(basename5))
1866
2109
  return;
1867
- const listener = async (path11, newStats) => {
2110
+ const listener = async (path12, newStats) => {
1868
2111
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
1869
2112
  return;
1870
2113
  if (!newStats || newStats.mtimeMs === 0) {
@@ -1878,18 +2121,18 @@ var NodeFsHandler = class {
1878
2121
  this.fsw._emit(EV.CHANGE, file, newStats2);
1879
2122
  }
1880
2123
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
1881
- this.fsw._closeFile(path11);
2124
+ this.fsw._closeFile(path12);
1882
2125
  prevStats = newStats2;
1883
2126
  const closer2 = this._watchWithNodeFs(file, listener);
1884
2127
  if (closer2)
1885
- this.fsw._addPathCloser(path11, closer2);
2128
+ this.fsw._addPathCloser(path12, closer2);
1886
2129
  } else {
1887
2130
  prevStats = newStats2;
1888
2131
  }
1889
2132
  } catch (error) {
1890
- this.fsw._remove(dirname5, basename4);
2133
+ this.fsw._remove(dirname8, basename5);
1891
2134
  }
1892
- } else if (parent.has(basename4)) {
2135
+ } else if (parent.has(basename5)) {
1893
2136
  const at = newStats.atimeMs;
1894
2137
  const mt = newStats.mtimeMs;
1895
2138
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -1914,7 +2157,7 @@ var NodeFsHandler = class {
1914
2157
  * @param item basename of this item
1915
2158
  * @returns true if no more processing is needed for this entry.
1916
2159
  */
1917
- async _handleSymlink(entry, directory, path11, item) {
2160
+ async _handleSymlink(entry, directory, path12, item) {
1918
2161
  if (this.fsw.closed) {
1919
2162
  return;
1920
2163
  }
@@ -1924,7 +2167,7 @@ var NodeFsHandler = class {
1924
2167
  this.fsw._incrReadyCount();
1925
2168
  let linkPath;
1926
2169
  try {
1927
- linkPath = await fsrealpath(path11);
2170
+ linkPath = await fsrealpath(path12);
1928
2171
  } catch (e) {
1929
2172
  this.fsw._emitReady();
1930
2173
  return true;
@@ -1934,12 +2177,12 @@ var NodeFsHandler = class {
1934
2177
  if (dir.has(item)) {
1935
2178
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
1936
2179
  this.fsw._symlinkPaths.set(full, linkPath);
1937
- this.fsw._emit(EV.CHANGE, path11, entry.stats);
2180
+ this.fsw._emit(EV.CHANGE, path12, entry.stats);
1938
2181
  }
1939
2182
  } else {
1940
2183
  dir.add(item);
1941
2184
  this.fsw._symlinkPaths.set(full, linkPath);
1942
- this.fsw._emit(EV.ADD, path11, entry.stats);
2185
+ this.fsw._emit(EV.ADD, path12, entry.stats);
1943
2186
  }
1944
2187
  this.fsw._emitReady();
1945
2188
  return true;
@@ -1969,9 +2212,9 @@ var NodeFsHandler = class {
1969
2212
  return;
1970
2213
  }
1971
2214
  const item = entry.path;
1972
- let path11 = sp.join(directory, item);
2215
+ let path12 = sp.join(directory, item);
1973
2216
  current.add(item);
1974
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path11, item)) {
2217
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path12, item)) {
1975
2218
  return;
1976
2219
  }
1977
2220
  if (this.fsw.closed) {
@@ -1980,11 +2223,11 @@ var NodeFsHandler = class {
1980
2223
  }
1981
2224
  if (item === target || !target && !previous.has(item)) {
1982
2225
  this.fsw._incrReadyCount();
1983
- path11 = sp.join(dir, sp.relative(dir, path11));
1984
- this._addToNodeFs(path11, initialAdd, wh, depth + 1);
2226
+ path12 = sp.join(dir, sp.relative(dir, path12));
2227
+ this._addToNodeFs(path12, initialAdd, wh, depth + 1);
1985
2228
  }
1986
2229
  }).on(EV.ERROR, this._boundHandleError);
1987
- return new Promise((resolve7, reject) => {
2230
+ return new Promise((resolve9, reject) => {
1988
2231
  if (!stream)
1989
2232
  return reject();
1990
2233
  stream.once(STR_END, () => {
@@ -1993,7 +2236,7 @@ var NodeFsHandler = class {
1993
2236
  return;
1994
2237
  }
1995
2238
  const wasThrottled = throttler ? throttler.clear() : false;
1996
- resolve7(void 0);
2239
+ resolve9(void 0);
1997
2240
  previous.getChildren().filter((item) => {
1998
2241
  return item !== directory && !current.has(item);
1999
2242
  }).forEach((item) => {
@@ -2050,13 +2293,13 @@ var NodeFsHandler = class {
2050
2293
  * @param depth Child path actually targeted for watch
2051
2294
  * @param target Child path actually targeted for watch
2052
2295
  */
2053
- async _addToNodeFs(path11, initialAdd, priorWh, depth, target) {
2296
+ async _addToNodeFs(path12, initialAdd, priorWh, depth, target) {
2054
2297
  const ready = this.fsw._emitReady;
2055
- if (this.fsw._isIgnored(path11) || this.fsw.closed) {
2298
+ if (this.fsw._isIgnored(path12) || this.fsw.closed) {
2056
2299
  ready();
2057
2300
  return false;
2058
2301
  }
2059
- const wh = this.fsw._getWatchHelpers(path11);
2302
+ const wh = this.fsw._getWatchHelpers(path12);
2060
2303
  if (priorWh) {
2061
2304
  wh.filterPath = (entry) => priorWh.filterPath(entry);
2062
2305
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -2072,8 +2315,8 @@ var NodeFsHandler = class {
2072
2315
  const follow = this.fsw.options.followSymlinks;
2073
2316
  let closer;
2074
2317
  if (stats.isDirectory()) {
2075
- const absPath = sp.resolve(path11);
2076
- const targetPath = follow ? await fsrealpath(path11) : path11;
2318
+ const absPath = sp.resolve(path12);
2319
+ const targetPath = follow ? await fsrealpath(path12) : path12;
2077
2320
  if (this.fsw.closed)
2078
2321
  return;
2079
2322
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -2083,29 +2326,29 @@ var NodeFsHandler = class {
2083
2326
  this.fsw._symlinkPaths.set(absPath, targetPath);
2084
2327
  }
2085
2328
  } else if (stats.isSymbolicLink()) {
2086
- const targetPath = follow ? await fsrealpath(path11) : path11;
2329
+ const targetPath = follow ? await fsrealpath(path12) : path12;
2087
2330
  if (this.fsw.closed)
2088
2331
  return;
2089
2332
  const parent = sp.dirname(wh.watchPath);
2090
2333
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
2091
2334
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
2092
- closer = await this._handleDir(parent, stats, initialAdd, depth, path11, wh, targetPath);
2335
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path12, wh, targetPath);
2093
2336
  if (this.fsw.closed)
2094
2337
  return;
2095
2338
  if (targetPath !== void 0) {
2096
- this.fsw._symlinkPaths.set(sp.resolve(path11), targetPath);
2339
+ this.fsw._symlinkPaths.set(sp.resolve(path12), targetPath);
2097
2340
  }
2098
2341
  } else {
2099
2342
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
2100
2343
  }
2101
2344
  ready();
2102
2345
  if (closer)
2103
- this.fsw._addPathCloser(path11, closer);
2346
+ this.fsw._addPathCloser(path12, closer);
2104
2347
  return false;
2105
2348
  } catch (error) {
2106
2349
  if (this.fsw._handleError(error)) {
2107
2350
  ready();
2108
- return path11;
2351
+ return path12;
2109
2352
  }
2110
2353
  }
2111
2354
  }
@@ -2137,35 +2380,35 @@ function createPattern(matcher) {
2137
2380
  if (matcher.path === string)
2138
2381
  return true;
2139
2382
  if (matcher.recursive) {
2140
- const relative6 = sp2.relative(matcher.path, string);
2141
- if (!relative6) {
2383
+ const relative8 = sp2.relative(matcher.path, string);
2384
+ if (!relative8) {
2142
2385
  return false;
2143
2386
  }
2144
- return !relative6.startsWith("..") && !sp2.isAbsolute(relative6);
2387
+ return !relative8.startsWith("..") && !sp2.isAbsolute(relative8);
2145
2388
  }
2146
2389
  return false;
2147
2390
  };
2148
2391
  }
2149
2392
  return () => false;
2150
2393
  }
2151
- function normalizePath(path11) {
2152
- if (typeof path11 !== "string")
2394
+ function normalizePath(path12) {
2395
+ if (typeof path12 !== "string")
2153
2396
  throw new Error("string expected");
2154
- path11 = sp2.normalize(path11);
2155
- path11 = path11.replace(/\\/g, "/");
2397
+ path12 = sp2.normalize(path12);
2398
+ path12 = path12.replace(/\\/g, "/");
2156
2399
  let prepend = false;
2157
- if (path11.startsWith("//"))
2400
+ if (path12.startsWith("//"))
2158
2401
  prepend = true;
2159
- path11 = path11.replace(DOUBLE_SLASH_RE, "/");
2402
+ path12 = path12.replace(DOUBLE_SLASH_RE, "/");
2160
2403
  if (prepend)
2161
- path11 = "/" + path11;
2162
- return path11;
2404
+ path12 = "/" + path12;
2405
+ return path12;
2163
2406
  }
2164
2407
  function matchPatterns(patterns, testString, stats) {
2165
- const path11 = normalizePath(testString);
2408
+ const path12 = normalizePath(testString);
2166
2409
  for (let index = 0; index < patterns.length; index++) {
2167
2410
  const pattern = patterns[index];
2168
- if (pattern(path11, stats)) {
2411
+ if (pattern(path12, stats)) {
2169
2412
  return true;
2170
2413
  }
2171
2414
  }
@@ -2203,19 +2446,19 @@ var toUnix = (string) => {
2203
2446
  }
2204
2447
  return str;
2205
2448
  };
2206
- var normalizePathToUnix = (path11) => toUnix(sp2.normalize(toUnix(path11)));
2207
- var normalizeIgnored = (cwd = "") => (path11) => {
2208
- if (typeof path11 === "string") {
2209
- return normalizePathToUnix(sp2.isAbsolute(path11) ? path11 : sp2.join(cwd, path11));
2449
+ var normalizePathToUnix = (path12) => toUnix(sp2.normalize(toUnix(path12)));
2450
+ var normalizeIgnored = (cwd = "") => (path12) => {
2451
+ if (typeof path12 === "string") {
2452
+ return normalizePathToUnix(sp2.isAbsolute(path12) ? path12 : sp2.join(cwd, path12));
2210
2453
  } else {
2211
- return path11;
2454
+ return path12;
2212
2455
  }
2213
2456
  };
2214
- var getAbsolutePath = (path11, cwd) => {
2215
- if (sp2.isAbsolute(path11)) {
2216
- return path11;
2457
+ var getAbsolutePath = (path12, cwd) => {
2458
+ if (sp2.isAbsolute(path12)) {
2459
+ return path12;
2217
2460
  }
2218
- return sp2.join(cwd, path11);
2461
+ return sp2.join(cwd, path12);
2219
2462
  };
2220
2463
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
2221
2464
  var DirEntry = class {
@@ -2280,10 +2523,10 @@ var WatchHelper = class {
2280
2523
  dirParts;
2281
2524
  followSymlinks;
2282
2525
  statMethod;
2283
- constructor(path11, follow, fsw) {
2526
+ constructor(path12, follow, fsw) {
2284
2527
  this.fsw = fsw;
2285
- const watchPath = path11;
2286
- this.path = path11 = path11.replace(REPLACER_RE, "");
2528
+ const watchPath = path12;
2529
+ this.path = path12 = path12.replace(REPLACER_RE, "");
2287
2530
  this.watchPath = watchPath;
2288
2531
  this.fullWatchPath = sp2.resolve(watchPath);
2289
2532
  this.dirParts = [];
@@ -2423,20 +2666,20 @@ var FSWatcher = class extends EventEmitter {
2423
2666
  this._closePromise = void 0;
2424
2667
  let paths = unifyPaths(paths_);
2425
2668
  if (cwd) {
2426
- paths = paths.map((path11) => {
2427
- const absPath = getAbsolutePath(path11, cwd);
2669
+ paths = paths.map((path12) => {
2670
+ const absPath = getAbsolutePath(path12, cwd);
2428
2671
  return absPath;
2429
2672
  });
2430
2673
  }
2431
- paths.forEach((path11) => {
2432
- this._removeIgnoredPath(path11);
2674
+ paths.forEach((path12) => {
2675
+ this._removeIgnoredPath(path12);
2433
2676
  });
2434
2677
  this._userIgnored = void 0;
2435
2678
  if (!this._readyCount)
2436
2679
  this._readyCount = 0;
2437
2680
  this._readyCount += paths.length;
2438
- Promise.all(paths.map(async (path11) => {
2439
- const res = await this._nodeFsHandler._addToNodeFs(path11, !_internal, void 0, 0, _origAdd);
2681
+ Promise.all(paths.map(async (path12) => {
2682
+ const res = await this._nodeFsHandler._addToNodeFs(path12, !_internal, void 0, 0, _origAdd);
2440
2683
  if (res)
2441
2684
  this._emitReady();
2442
2685
  return res;
@@ -2458,17 +2701,17 @@ var FSWatcher = class extends EventEmitter {
2458
2701
  return this;
2459
2702
  const paths = unifyPaths(paths_);
2460
2703
  const { cwd } = this.options;
2461
- paths.forEach((path11) => {
2462
- if (!sp2.isAbsolute(path11) && !this._closers.has(path11)) {
2704
+ paths.forEach((path12) => {
2705
+ if (!sp2.isAbsolute(path12) && !this._closers.has(path12)) {
2463
2706
  if (cwd)
2464
- path11 = sp2.join(cwd, path11);
2465
- path11 = sp2.resolve(path11);
2707
+ path12 = sp2.join(cwd, path12);
2708
+ path12 = sp2.resolve(path12);
2466
2709
  }
2467
- this._closePath(path11);
2468
- this._addIgnoredPath(path11);
2469
- if (this._watched.has(path11)) {
2710
+ this._closePath(path12);
2711
+ this._addIgnoredPath(path12);
2712
+ if (this._watched.has(path12)) {
2470
2713
  this._addIgnoredPath({
2471
- path: path11,
2714
+ path: path12,
2472
2715
  recursive: true
2473
2716
  });
2474
2717
  }
@@ -2532,38 +2775,38 @@ var FSWatcher = class extends EventEmitter {
2532
2775
  * @param stats arguments to be passed with event
2533
2776
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
2534
2777
  */
2535
- async _emit(event, path11, stats) {
2778
+ async _emit(event, path12, stats) {
2536
2779
  if (this.closed)
2537
2780
  return;
2538
2781
  const opts = this.options;
2539
2782
  if (isWindows)
2540
- path11 = sp2.normalize(path11);
2783
+ path12 = sp2.normalize(path12);
2541
2784
  if (opts.cwd)
2542
- path11 = sp2.relative(opts.cwd, path11);
2543
- const args = [path11];
2785
+ path12 = sp2.relative(opts.cwd, path12);
2786
+ const args = [path12];
2544
2787
  if (stats != null)
2545
2788
  args.push(stats);
2546
2789
  const awf = opts.awaitWriteFinish;
2547
2790
  let pw;
2548
- if (awf && (pw = this._pendingWrites.get(path11))) {
2791
+ if (awf && (pw = this._pendingWrites.get(path12))) {
2549
2792
  pw.lastChange = /* @__PURE__ */ new Date();
2550
2793
  return this;
2551
2794
  }
2552
2795
  if (opts.atomic) {
2553
2796
  if (event === EVENTS.UNLINK) {
2554
- this._pendingUnlinks.set(path11, [event, ...args]);
2797
+ this._pendingUnlinks.set(path12, [event, ...args]);
2555
2798
  setTimeout(() => {
2556
- this._pendingUnlinks.forEach((entry, path12) => {
2799
+ this._pendingUnlinks.forEach((entry, path13) => {
2557
2800
  this.emit(...entry);
2558
2801
  this.emit(EVENTS.ALL, ...entry);
2559
- this._pendingUnlinks.delete(path12);
2802
+ this._pendingUnlinks.delete(path13);
2560
2803
  });
2561
2804
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
2562
2805
  return this;
2563
2806
  }
2564
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path11)) {
2807
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path12)) {
2565
2808
  event = EVENTS.CHANGE;
2566
- this._pendingUnlinks.delete(path11);
2809
+ this._pendingUnlinks.delete(path12);
2567
2810
  }
2568
2811
  }
2569
2812
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -2581,16 +2824,16 @@ var FSWatcher = class extends EventEmitter {
2581
2824
  this.emitWithAll(event, args);
2582
2825
  }
2583
2826
  };
2584
- this._awaitWriteFinish(path11, awf.stabilityThreshold, event, awfEmit);
2827
+ this._awaitWriteFinish(path12, awf.stabilityThreshold, event, awfEmit);
2585
2828
  return this;
2586
2829
  }
2587
2830
  if (event === EVENTS.CHANGE) {
2588
- const isThrottled = !this._throttle(EVENTS.CHANGE, path11, 50);
2831
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path12, 50);
2589
2832
  if (isThrottled)
2590
2833
  return this;
2591
2834
  }
2592
2835
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
2593
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path11) : path11;
2836
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path12) : path12;
2594
2837
  let stats2;
2595
2838
  try {
2596
2839
  stats2 = await stat3(fullPath);
@@ -2621,23 +2864,23 @@ var FSWatcher = class extends EventEmitter {
2621
2864
  * @param timeout duration of time to suppress duplicate actions
2622
2865
  * @returns tracking object or false if action should be suppressed
2623
2866
  */
2624
- _throttle(actionType, path11, timeout) {
2867
+ _throttle(actionType, path12, timeout) {
2625
2868
  if (!this._throttled.has(actionType)) {
2626
2869
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
2627
2870
  }
2628
2871
  const action = this._throttled.get(actionType);
2629
2872
  if (!action)
2630
2873
  throw new Error("invalid throttle");
2631
- const actionPath = action.get(path11);
2874
+ const actionPath = action.get(path12);
2632
2875
  if (actionPath) {
2633
2876
  actionPath.count++;
2634
2877
  return false;
2635
2878
  }
2636
2879
  let timeoutObject;
2637
2880
  const clear = () => {
2638
- const item = action.get(path11);
2881
+ const item = action.get(path12);
2639
2882
  const count = item ? item.count : 0;
2640
- action.delete(path11);
2883
+ action.delete(path12);
2641
2884
  clearTimeout(timeoutObject);
2642
2885
  if (item)
2643
2886
  clearTimeout(item.timeoutObject);
@@ -2645,7 +2888,7 @@ var FSWatcher = class extends EventEmitter {
2645
2888
  };
2646
2889
  timeoutObject = setTimeout(clear, timeout);
2647
2890
  const thr = { timeoutObject, clear, count: 0 };
2648
- action.set(path11, thr);
2891
+ action.set(path12, thr);
2649
2892
  return thr;
2650
2893
  }
2651
2894
  _incrReadyCount() {
@@ -2659,44 +2902,44 @@ var FSWatcher = class extends EventEmitter {
2659
2902
  * @param event
2660
2903
  * @param awfEmit Callback to be called when ready for event to be emitted.
2661
2904
  */
2662
- _awaitWriteFinish(path11, threshold, event, awfEmit) {
2905
+ _awaitWriteFinish(path12, threshold, event, awfEmit) {
2663
2906
  const awf = this.options.awaitWriteFinish;
2664
2907
  if (typeof awf !== "object")
2665
2908
  return;
2666
2909
  const pollInterval = awf.pollInterval;
2667
2910
  let timeoutHandler;
2668
- let fullPath = path11;
2669
- if (this.options.cwd && !sp2.isAbsolute(path11)) {
2670
- fullPath = sp2.join(this.options.cwd, path11);
2911
+ let fullPath = path12;
2912
+ if (this.options.cwd && !sp2.isAbsolute(path12)) {
2913
+ fullPath = sp2.join(this.options.cwd, path12);
2671
2914
  }
2672
2915
  const now = /* @__PURE__ */ new Date();
2673
2916
  const writes = this._pendingWrites;
2674
2917
  function awaitWriteFinishFn(prevStat) {
2675
2918
  statcb(fullPath, (err, curStat) => {
2676
- if (err || !writes.has(path11)) {
2919
+ if (err || !writes.has(path12)) {
2677
2920
  if (err && err.code !== "ENOENT")
2678
2921
  awfEmit(err);
2679
2922
  return;
2680
2923
  }
2681
2924
  const now2 = Number(/* @__PURE__ */ new Date());
2682
2925
  if (prevStat && curStat.size !== prevStat.size) {
2683
- writes.get(path11).lastChange = now2;
2926
+ writes.get(path12).lastChange = now2;
2684
2927
  }
2685
- const pw = writes.get(path11);
2928
+ const pw = writes.get(path12);
2686
2929
  const df = now2 - pw.lastChange;
2687
2930
  if (df >= threshold) {
2688
- writes.delete(path11);
2931
+ writes.delete(path12);
2689
2932
  awfEmit(void 0, curStat);
2690
2933
  } else {
2691
2934
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
2692
2935
  }
2693
2936
  });
2694
2937
  }
2695
- if (!writes.has(path11)) {
2696
- writes.set(path11, {
2938
+ if (!writes.has(path12)) {
2939
+ writes.set(path12, {
2697
2940
  lastChange: now,
2698
2941
  cancelWait: () => {
2699
- writes.delete(path11);
2942
+ writes.delete(path12);
2700
2943
  clearTimeout(timeoutHandler);
2701
2944
  return event;
2702
2945
  }
@@ -2707,8 +2950,8 @@ var FSWatcher = class extends EventEmitter {
2707
2950
  /**
2708
2951
  * Determines whether user has asked to ignore this path.
2709
2952
  */
2710
- _isIgnored(path11, stats) {
2711
- if (this.options.atomic && DOT_RE.test(path11))
2953
+ _isIgnored(path12, stats) {
2954
+ if (this.options.atomic && DOT_RE.test(path12))
2712
2955
  return true;
2713
2956
  if (!this._userIgnored) {
2714
2957
  const { cwd } = this.options;
@@ -2718,17 +2961,17 @@ var FSWatcher = class extends EventEmitter {
2718
2961
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
2719
2962
  this._userIgnored = anymatch(list, void 0);
2720
2963
  }
2721
- return this._userIgnored(path11, stats);
2964
+ return this._userIgnored(path12, stats);
2722
2965
  }
2723
- _isntIgnored(path11, stat4) {
2724
- return !this._isIgnored(path11, stat4);
2966
+ _isntIgnored(path12, stat4) {
2967
+ return !this._isIgnored(path12, stat4);
2725
2968
  }
2726
2969
  /**
2727
2970
  * Provides a set of common helpers and properties relating to symlink handling.
2728
2971
  * @param path file or directory pattern being watched
2729
2972
  */
2730
- _getWatchHelpers(path11) {
2731
- return new WatchHelper(path11, this.options.followSymlinks, this);
2973
+ _getWatchHelpers(path12) {
2974
+ return new WatchHelper(path12, this.options.followSymlinks, this);
2732
2975
  }
2733
2976
  // Directory helpers
2734
2977
  // -----------------
@@ -2760,63 +3003,63 @@ var FSWatcher = class extends EventEmitter {
2760
3003
  * @param item base path of item/directory
2761
3004
  */
2762
3005
  _remove(directory, item, isDirectory) {
2763
- const path11 = sp2.join(directory, item);
2764
- const fullPath = sp2.resolve(path11);
2765
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path11) || this._watched.has(fullPath);
2766
- if (!this._throttle("remove", path11, 100))
3006
+ const path12 = sp2.join(directory, item);
3007
+ const fullPath = sp2.resolve(path12);
3008
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path12) || this._watched.has(fullPath);
3009
+ if (!this._throttle("remove", path12, 100))
2767
3010
  return;
2768
3011
  if (!isDirectory && this._watched.size === 1) {
2769
3012
  this.add(directory, item, true);
2770
3013
  }
2771
- const wp = this._getWatchedDir(path11);
3014
+ const wp = this._getWatchedDir(path12);
2772
3015
  const nestedDirectoryChildren = wp.getChildren();
2773
- nestedDirectoryChildren.forEach((nested) => this._remove(path11, nested));
3016
+ nestedDirectoryChildren.forEach((nested) => this._remove(path12, nested));
2774
3017
  const parent = this._getWatchedDir(directory);
2775
3018
  const wasTracked = parent.has(item);
2776
3019
  parent.remove(item);
2777
3020
  if (this._symlinkPaths.has(fullPath)) {
2778
3021
  this._symlinkPaths.delete(fullPath);
2779
3022
  }
2780
- let relPath = path11;
3023
+ let relPath = path12;
2781
3024
  if (this.options.cwd)
2782
- relPath = sp2.relative(this.options.cwd, path11);
3025
+ relPath = sp2.relative(this.options.cwd, path12);
2783
3026
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
2784
3027
  const event = this._pendingWrites.get(relPath).cancelWait();
2785
3028
  if (event === EVENTS.ADD)
2786
3029
  return;
2787
3030
  }
2788
- this._watched.delete(path11);
3031
+ this._watched.delete(path12);
2789
3032
  this._watched.delete(fullPath);
2790
3033
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
2791
- if (wasTracked && !this._isIgnored(path11))
2792
- this._emit(eventName, path11);
2793
- this._closePath(path11);
3034
+ if (wasTracked && !this._isIgnored(path12))
3035
+ this._emit(eventName, path12);
3036
+ this._closePath(path12);
2794
3037
  }
2795
3038
  /**
2796
3039
  * Closes all watchers for a path
2797
3040
  */
2798
- _closePath(path11) {
2799
- this._closeFile(path11);
2800
- const dir = sp2.dirname(path11);
2801
- this._getWatchedDir(dir).remove(sp2.basename(path11));
3041
+ _closePath(path12) {
3042
+ this._closeFile(path12);
3043
+ const dir = sp2.dirname(path12);
3044
+ this._getWatchedDir(dir).remove(sp2.basename(path12));
2802
3045
  }
2803
3046
  /**
2804
3047
  * Closes only file-specific watchers
2805
3048
  */
2806
- _closeFile(path11) {
2807
- const closers = this._closers.get(path11);
3049
+ _closeFile(path12) {
3050
+ const closers = this._closers.get(path12);
2808
3051
  if (!closers)
2809
3052
  return;
2810
3053
  closers.forEach((closer) => closer());
2811
- this._closers.delete(path11);
3054
+ this._closers.delete(path12);
2812
3055
  }
2813
- _addPathCloser(path11, closer) {
3056
+ _addPathCloser(path12, closer) {
2814
3057
  if (!closer)
2815
3058
  return;
2816
- let list = this._closers.get(path11);
3059
+ let list = this._closers.get(path12);
2817
3060
  if (!list) {
2818
3061
  list = [];
2819
- this._closers.set(path11, list);
3062
+ this._closers.set(path12, list);
2820
3063
  }
2821
3064
  list.push(closer);
2822
3065
  }
@@ -2846,12 +3089,12 @@ function watch(paths, options = {}) {
2846
3089
  var chokidar_default = { watch, FSWatcher };
2847
3090
 
2848
3091
  // src/watcher/index.ts
2849
- import * as path4 from "path";
3092
+ import * as path5 from "path";
2850
3093
 
2851
3094
  // src/utils/files.ts
2852
3095
  var import_ignore = __toESM(require_ignore(), 1);
2853
- import { existsSync as existsSync2, readFileSync as readFileSync2, promises as fsPromises } from "fs";
2854
- import * as path2 from "path";
3096
+ import { existsSync as existsSync4, readFileSync as readFileSync3, promises as fsPromises } from "fs";
3097
+ import * as path4 from "path";
2855
3098
  var PROJECT_MARKERS = [
2856
3099
  ".git",
2857
3100
  "package.json",
@@ -2870,7 +3113,7 @@ var PROJECT_MARKERS = [
2870
3113
  ];
2871
3114
  function hasProjectMarker(projectRoot) {
2872
3115
  for (const marker of PROJECT_MARKERS) {
2873
- if (existsSync2(path2.join(projectRoot, marker))) {
3116
+ if (existsSync4(path4.join(projectRoot, marker))) {
2874
3117
  return true;
2875
3118
  }
2876
3119
  }
@@ -2896,16 +3139,16 @@ function createIgnoreFilter(projectRoot) {
2896
3139
  "**/*build*/**"
2897
3140
  ];
2898
3141
  ig.add(defaultIgnores);
2899
- const gitignorePath = path2.join(projectRoot, ".gitignore");
2900
- if (existsSync2(gitignorePath)) {
2901
- const gitignoreContent = readFileSync2(gitignorePath, "utf-8");
3142
+ const gitignorePath = path4.join(projectRoot, ".gitignore");
3143
+ if (existsSync4(gitignorePath)) {
3144
+ const gitignoreContent = readFileSync3(gitignorePath, "utf-8");
2902
3145
  ig.add(gitignoreContent);
2903
3146
  }
2904
3147
  return ig;
2905
3148
  }
2906
3149
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
2907
- const relativePath = path2.relative(projectRoot, filePath);
2908
- const pathParts = relativePath.split(path2.sep);
3150
+ const relativePath = path4.relative(projectRoot, filePath);
3151
+ const pathParts = relativePath.split(path4.sep);
2909
3152
  for (const part of pathParts) {
2910
3153
  if (part.startsWith(".") && part !== "." && part !== "..") {
2911
3154
  return false;
@@ -2949,8 +3192,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
2949
3192
  const filesInDir = [];
2950
3193
  const subdirs = [];
2951
3194
  for (const entry of entries) {
2952
- const fullPath = path2.join(dir, entry.name);
2953
- const relativePath = path2.relative(projectRoot, fullPath);
3195
+ const fullPath = path4.join(dir, entry.name);
3196
+ const relativePath = path4.relative(projectRoot, fullPath);
2954
3197
  if (entry.name.startsWith(".") && entry.name !== "." && entry.name !== "..") {
2955
3198
  if (entry.isDirectory()) {
2956
3199
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -2999,7 +3242,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
2999
3242
  yield f;
3000
3243
  }
3001
3244
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3002
- skipped.push({ path: path2.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3245
+ skipped.push({ path: path4.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3003
3246
  }
3004
3247
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3005
3248
  if (canRecurse) {
@@ -3039,8 +3282,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3039
3282
  if (additionalRoots && additionalRoots.length > 0) {
3040
3283
  const normalizedRoots = /* @__PURE__ */ new Set();
3041
3284
  for (const kbRoot of additionalRoots) {
3042
- const resolved = path2.normalize(
3043
- path2.isAbsolute(kbRoot) ? kbRoot : path2.resolve(projectRoot, kbRoot)
3285
+ const resolved = path4.normalize(
3286
+ path4.isAbsolute(kbRoot) ? kbRoot : path4.resolve(projectRoot, kbRoot)
3044
3287
  );
3045
3288
  normalizedRoots.add(resolved);
3046
3289
  }
@@ -3073,122 +3316,6 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3073
3316
  return { files, skipped };
3074
3317
  }
3075
3318
 
3076
- // src/git/index.ts
3077
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
3078
- import * as path3 from "path";
3079
- function readPackedRefs(gitDir) {
3080
- const packedRefsPath = path3.join(gitDir, "packed-refs");
3081
- if (!existsSync3(packedRefsPath)) {
3082
- return [];
3083
- }
3084
- try {
3085
- return readFileSync3(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
3086
- } catch {
3087
- return [];
3088
- }
3089
- }
3090
- function resolveCommonGitDir(gitDir) {
3091
- const commonDirPath = path3.join(gitDir, "commondir");
3092
- if (!existsSync3(commonDirPath)) {
3093
- return gitDir;
3094
- }
3095
- try {
3096
- const raw = readFileSync3(commonDirPath, "utf-8").trim();
3097
- if (!raw) {
3098
- return gitDir;
3099
- }
3100
- const resolved = path3.isAbsolute(raw) ? raw : path3.resolve(gitDir, raw);
3101
- if (existsSync3(resolved)) {
3102
- return resolved;
3103
- }
3104
- } catch {
3105
- return gitDir;
3106
- }
3107
- return gitDir;
3108
- }
3109
- function resolveGitDir(repoRoot) {
3110
- const gitPath = path3.join(repoRoot, ".git");
3111
- if (!existsSync3(gitPath)) {
3112
- return null;
3113
- }
3114
- try {
3115
- const stat4 = statSync(gitPath);
3116
- if (stat4.isDirectory()) {
3117
- return gitPath;
3118
- }
3119
- if (stat4.isFile()) {
3120
- const content = readFileSync3(gitPath, "utf-8").trim();
3121
- const match = content.match(/^gitdir:\s*(.+)$/);
3122
- if (match) {
3123
- const gitdir = match[1];
3124
- const resolvedPath = path3.isAbsolute(gitdir) ? gitdir : path3.resolve(repoRoot, gitdir);
3125
- if (existsSync3(resolvedPath)) {
3126
- return resolvedPath;
3127
- }
3128
- }
3129
- }
3130
- } catch {
3131
- }
3132
- return null;
3133
- }
3134
- function isGitRepo(dir) {
3135
- return resolveGitDir(dir) !== null;
3136
- }
3137
- function getCurrentBranch(repoRoot) {
3138
- const gitDir = resolveGitDir(repoRoot);
3139
- if (!gitDir) {
3140
- return null;
3141
- }
3142
- const headPath = path3.join(gitDir, "HEAD");
3143
- if (!existsSync3(headPath)) {
3144
- return null;
3145
- }
3146
- try {
3147
- const headContent = readFileSync3(headPath, "utf-8").trim();
3148
- const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
3149
- if (match) {
3150
- return match[1];
3151
- }
3152
- if (/^[0-9a-f]{40}$/i.test(headContent)) {
3153
- return headContent.slice(0, 7);
3154
- }
3155
- return null;
3156
- } catch {
3157
- return null;
3158
- }
3159
- }
3160
- function getBaseBranch(repoRoot) {
3161
- const gitDir = resolveGitDir(repoRoot);
3162
- const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
3163
- const candidates = ["main", "master", "develop", "trunk"];
3164
- if (refStoreDir) {
3165
- for (const candidate of candidates) {
3166
- const refPath = path3.join(refStoreDir, "refs", "heads", candidate);
3167
- if (existsSync3(refPath)) {
3168
- return candidate;
3169
- }
3170
- const packedRefs = readPackedRefs(refStoreDir);
3171
- if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
3172
- return candidate;
3173
- }
3174
- }
3175
- }
3176
- return getCurrentBranch(repoRoot) ?? "main";
3177
- }
3178
- function getBranchOrDefault(repoRoot) {
3179
- if (!isGitRepo(repoRoot)) {
3180
- return "default";
3181
- }
3182
- return getCurrentBranch(repoRoot) ?? "default";
3183
- }
3184
- function getHeadPath(repoRoot) {
3185
- const gitDir = resolveGitDir(repoRoot);
3186
- if (gitDir) {
3187
- return path3.join(gitDir, "HEAD");
3188
- }
3189
- return path3.join(repoRoot, ".git", "HEAD");
3190
- }
3191
-
3192
3319
  // src/watcher/index.ts
3193
3320
  var FileWatcher = class {
3194
3321
  watcher = null;
@@ -3210,9 +3337,9 @@ var FileWatcher = class {
3210
3337
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
3211
3338
  this.watcher = chokidar_default.watch(this.projectRoot, {
3212
3339
  ignored: (filePath) => {
3213
- const relativePath = path4.relative(this.projectRoot, filePath);
3340
+ const relativePath = path5.relative(this.projectRoot, filePath);
3214
3341
  if (!relativePath) return false;
3215
- const pathParts = relativePath.split(path4.sep);
3342
+ const pathParts = relativePath.split(path5.sep);
3216
3343
  for (const part of pathParts) {
3217
3344
  if (part.startsWith(".") && part !== "." && part !== "..") {
3218
3345
  return true;
@@ -3264,7 +3391,7 @@ var FileWatcher = class {
3264
3391
  return;
3265
3392
  }
3266
3393
  const changes = Array.from(this.pendingChanges.entries()).map(
3267
- ([path11, type]) => ({ path: path11, type })
3394
+ ([path12, type]) => ({ path: path12, type })
3268
3395
  );
3269
3396
  this.pendingChanges.clear();
3270
3397
  try {
@@ -3310,7 +3437,7 @@ var GitHeadWatcher = class {
3310
3437
  this.onBranchChange = handler;
3311
3438
  this.currentBranch = getCurrentBranch(this.projectRoot);
3312
3439
  const headPath = getHeadPath(this.projectRoot);
3313
- const refsPath = path4.join(this.projectRoot, ".git", "refs", "heads");
3440
+ const refsPath = path5.join(this.projectRoot, ".git", "refs", "heads");
3314
3441
  this.watcher = chokidar_default.watch([headPath, refsPath], {
3315
3442
  persistent: true,
3316
3443
  ignoreInitial: true,
@@ -3395,8 +3522,8 @@ function createWatcherWithIndexer(getIndexer2, projectRoot, config) {
3395
3522
  import { tool } from "@opencode-ai/plugin";
3396
3523
 
3397
3524
  // src/indexer/index.ts
3398
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
3399
- import * as path7 from "path";
3525
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
3526
+ import * as path8 from "path";
3400
3527
  import { performance as performance2 } from "perf_hooks";
3401
3528
 
3402
3529
  // node_modules/eventemitter3/index.mjs
@@ -3421,7 +3548,7 @@ function pTimeout(promise, options) {
3421
3548
  } = options;
3422
3549
  let timer;
3423
3550
  let abortHandler;
3424
- const wrappedPromise = new Promise((resolve7, reject) => {
3551
+ const wrappedPromise = new Promise((resolve9, reject) => {
3425
3552
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
3426
3553
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
3427
3554
  }
@@ -3435,7 +3562,7 @@ function pTimeout(promise, options) {
3435
3562
  };
3436
3563
  signal.addEventListener("abort", abortHandler, { once: true });
3437
3564
  }
3438
- promise.then(resolve7, reject);
3565
+ promise.then(resolve9, reject);
3439
3566
  if (milliseconds === Number.POSITIVE_INFINITY) {
3440
3567
  return;
3441
3568
  }
@@ -3443,7 +3570,7 @@ function pTimeout(promise, options) {
3443
3570
  timer = customTimers.setTimeout.call(void 0, () => {
3444
3571
  if (fallback) {
3445
3572
  try {
3446
- resolve7(fallback());
3573
+ resolve9(fallback());
3447
3574
  } catch (error) {
3448
3575
  reject(error);
3449
3576
  }
@@ -3453,7 +3580,7 @@ function pTimeout(promise, options) {
3453
3580
  promise.cancel();
3454
3581
  }
3455
3582
  if (message === false) {
3456
- resolve7();
3583
+ resolve9();
3457
3584
  } else if (message instanceof Error) {
3458
3585
  reject(message);
3459
3586
  } else {
@@ -3855,7 +3982,7 @@ var PQueue = class extends import_index.default {
3855
3982
  // Assign unique ID if not provided
3856
3983
  id: options.id ?? (this.#idAssigner++).toString()
3857
3984
  };
3858
- return new Promise((resolve7, reject) => {
3985
+ return new Promise((resolve9, reject) => {
3859
3986
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
3860
3987
  let cleanupQueueAbortHandler = () => void 0;
3861
3988
  const run = async () => {
@@ -3895,7 +4022,7 @@ var PQueue = class extends import_index.default {
3895
4022
  })]);
3896
4023
  }
3897
4024
  const result = await operation;
3898
- resolve7(result);
4025
+ resolve9(result);
3899
4026
  this.emit("completed", result);
3900
4027
  } catch (error) {
3901
4028
  reject(error);
@@ -4083,13 +4210,13 @@ var PQueue = class extends import_index.default {
4083
4210
  });
4084
4211
  }
4085
4212
  async #onEvent(event, filter) {
4086
- return new Promise((resolve7) => {
4213
+ return new Promise((resolve9) => {
4087
4214
  const listener = () => {
4088
4215
  if (filter && !filter()) {
4089
4216
  return;
4090
4217
  }
4091
4218
  this.off(event, listener);
4092
- resolve7();
4219
+ resolve9();
4093
4220
  };
4094
4221
  this.on(event, listener);
4095
4222
  });
@@ -4375,7 +4502,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4375
4502
  const finalDelay = Math.min(delayTime, remainingTime);
4376
4503
  options.signal?.throwIfAborted();
4377
4504
  if (finalDelay > 0) {
4378
- await new Promise((resolve7, reject) => {
4505
+ await new Promise((resolve9, reject) => {
4379
4506
  const onAbort = () => {
4380
4507
  clearTimeout(timeoutToken);
4381
4508
  options.signal?.removeEventListener("abort", onAbort);
@@ -4383,7 +4510,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
4383
4510
  };
4384
4511
  const timeoutToken = setTimeout(() => {
4385
4512
  options.signal?.removeEventListener("abort", onAbort);
4386
- resolve7();
4513
+ resolve9();
4387
4514
  }, finalDelay);
4388
4515
  if (options.unref) {
4389
4516
  timeoutToken.unref?.();
@@ -4444,16 +4571,16 @@ async function pRetry(input, options = {}) {
4444
4571
  }
4445
4572
 
4446
4573
  // src/embeddings/detector.ts
4447
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
4448
- import * as path5 from "path";
4449
- import * as os2 from "os";
4574
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4575
+ import * as path6 from "path";
4576
+ import * as os3 from "os";
4450
4577
  function getOpenCodeAuthPath() {
4451
- return path5.join(os2.homedir(), ".local", "share", "opencode", "auth.json");
4578
+ return path6.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
4452
4579
  }
4453
4580
  function loadOpenCodeAuth() {
4454
4581
  const authPath = getOpenCodeAuthPath();
4455
4582
  try {
4456
- if (existsSync4(authPath)) {
4583
+ if (existsSync5(authPath)) {
4457
4584
  return JSON.parse(readFileSync4(authPath, "utf-8"));
4458
4585
  }
4459
4586
  } catch {
@@ -4487,7 +4614,7 @@ async function detectEmbeddingProvider(preferredProvider, model) {
4487
4614
  );
4488
4615
  }
4489
4616
  async function tryDetectProvider() {
4490
- for (const provider of availableProviders) {
4617
+ for (const provider of autoDetectProviders) {
4491
4618
  const credentials = await getProviderCredentials(provider);
4492
4619
  if (credentials) {
4493
4620
  return {
@@ -4498,7 +4625,7 @@ async function tryDetectProvider() {
4498
4625
  }
4499
4626
  }
4500
4627
  throw new Error(
4501
- `No embedding-capable provider found. Please authenticate with OpenCode using one of: ${availableProviders.join(", ")}.`
4628
+ `No embedding-capable provider found. Please authenticate with OpenCode using one of: ${autoDetectProviders.join(", ")}.`
4502
4629
  );
4503
4630
  }
4504
4631
  async function getProviderCredentials(provider) {
@@ -4818,11 +4945,12 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider {
4818
4945
  return this.modelInfo;
4819
4946
  }
4820
4947
  };
4821
- var OllamaEmbeddingProvider = class {
4948
+ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider {
4822
4949
  constructor(credentials, modelInfo) {
4823
4950
  this.credentials = credentials;
4824
4951
  this.modelInfo = modelInfo;
4825
4952
  }
4953
+ static MIN_TRUNCATION_CHARS = 512;
4826
4954
  async embedQuery(query) {
4827
4955
  const result = await this.embedBatch([query]);
4828
4956
  return {
@@ -4837,30 +4965,103 @@ var OllamaEmbeddingProvider = class {
4837
4965
  tokensUsed: result.totalTokensUsed
4838
4966
  };
4839
4967
  }
4840
- async embedBatch(texts) {
4841
- const results = await Promise.all(
4842
- texts.map(async (text) => {
4843
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
4844
- method: "POST",
4845
- headers: {
4846
- "Content-Type": "application/json"
4847
- },
4848
- body: JSON.stringify({
4849
- model: this.modelInfo.model,
4850
- prompt: text
4851
- })
4852
- });
4853
- if (!response.ok) {
4854
- const error = await response.text();
4855
- throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
4856
- }
4857
- const data = await response.json();
4858
- return {
4859
- embedding: data.embedding,
4860
- tokensUsed: Math.ceil(text.length / 4)
4861
- };
4862
- })
4968
+ estimateTokens(text) {
4969
+ return Math.ceil(text.length / 4);
4970
+ }
4971
+ truncateToCharLimit(text, maxChars) {
4972
+ if (text.length <= maxChars) {
4973
+ return text;
4974
+ }
4975
+ return `${text.slice(0, Math.max(0, maxChars - 17))}
4976
+ ... [truncated]`;
4977
+ }
4978
+ isContextLengthError(error) {
4979
+ const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
4980
+ 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");
4981
+ }
4982
+ buildTruncationCandidates(text) {
4983
+ const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
4984
+ const candidateLimits = /* @__PURE__ */ new Set();
4985
+ const baselineLimit = text.length > baseMaxChars ? baseMaxChars : Math.max(
4986
+ _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
4987
+ Math.floor(text.length * 0.9)
4863
4988
  );
4989
+ if (baselineLimit < text.length) {
4990
+ candidateLimits.add(baselineLimit);
4991
+ }
4992
+ for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
4993
+ const scaledLimit = Math.max(
4994
+ _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
4995
+ Math.floor(baselineLimit * factor)
4996
+ );
4997
+ if (scaledLimit < text.length) {
4998
+ candidateLimits.add(scaledLimit);
4999
+ }
5000
+ }
5001
+ candidateLimits.add(Math.min(text.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
5002
+ const candidates = [];
5003
+ const seen = /* @__PURE__ */ new Set();
5004
+ for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
5005
+ if (limit <= 0 || limit >= text.length) {
5006
+ continue;
5007
+ }
5008
+ const truncated = this.truncateToCharLimit(text, limit);
5009
+ if (truncated === text || seen.has(truncated)) {
5010
+ continue;
5011
+ }
5012
+ seen.add(truncated);
5013
+ candidates.push(truncated);
5014
+ }
5015
+ return candidates;
5016
+ }
5017
+ async embedSingleWithFallback(text) {
5018
+ try {
5019
+ return await this.embedSingle(text);
5020
+ } catch (error) {
5021
+ if (!this.isContextLengthError(error)) {
5022
+ throw error;
5023
+ }
5024
+ let lastError = error;
5025
+ for (const truncated of this.buildTruncationCandidates(text)) {
5026
+ try {
5027
+ return await this.embedSingle(truncated);
5028
+ } catch (retryError) {
5029
+ if (!this.isContextLengthError(retryError)) {
5030
+ throw retryError;
5031
+ }
5032
+ lastError = retryError;
5033
+ }
5034
+ }
5035
+ throw lastError;
5036
+ }
5037
+ }
5038
+ async embedSingle(text) {
5039
+ const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
5040
+ method: "POST",
5041
+ headers: {
5042
+ "Content-Type": "application/json"
5043
+ },
5044
+ body: JSON.stringify({
5045
+ model: this.modelInfo.model,
5046
+ prompt: text,
5047
+ truncate: false
5048
+ })
5049
+ });
5050
+ if (!response.ok) {
5051
+ const error = await response.text();
5052
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
5053
+ }
5054
+ const data = await response.json();
5055
+ return {
5056
+ embedding: data.embedding,
5057
+ tokensUsed: this.estimateTokens(text)
5058
+ };
5059
+ }
5060
+ async embedBatch(texts) {
5061
+ const results = [];
5062
+ for (const text of texts) {
5063
+ results.push(await this.embedSingleWithFallback(text));
5064
+ }
4864
5065
  return {
4865
5066
  embeddings: results.map((r) => r.embedding),
4866
5067
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
@@ -5416,13 +5617,13 @@ function initializeLogger(config) {
5416
5617
  }
5417
5618
 
5418
5619
  // src/native/index.ts
5419
- import * as path6 from "path";
5420
- import * as os3 from "os";
5620
+ import * as path7 from "path";
5621
+ import * as os4 from "os";
5421
5622
  import * as module from "module";
5422
5623
  import { fileURLToPath } from "url";
5423
5624
  function getNativeBinding() {
5424
- const platform2 = os3.platform();
5425
- const arch2 = os3.arch();
5625
+ const platform2 = os4.platform();
5626
+ const arch2 = os4.arch();
5426
5627
  let bindingName;
5427
5628
  if (platform2 === "darwin" && arch2 === "arm64") {
5428
5629
  bindingName = "codebase-index-native.darwin-arm64.node";
@@ -5440,18 +5641,19 @@ function getNativeBinding() {
5440
5641
  let currentDir;
5441
5642
  let requireTarget;
5442
5643
  if (typeof import.meta !== "undefined" && import.meta.url) {
5443
- currentDir = path6.dirname(fileURLToPath(import.meta.url));
5644
+ currentDir = path7.dirname(fileURLToPath(import.meta.url));
5444
5645
  requireTarget = import.meta.url;
5445
5646
  } else if (typeof __dirname !== "undefined") {
5446
5647
  currentDir = __dirname;
5447
5648
  requireTarget = __filename;
5448
5649
  } else {
5449
5650
  currentDir = process.cwd();
5450
- requireTarget = path6.join(currentDir, "index.js");
5651
+ requireTarget = path7.join(currentDir, "index.js");
5451
5652
  }
5452
- const isDevMode = currentDir.includes("/src/native");
5453
- const packageRoot = isDevMode ? path6.resolve(currentDir, "../..") : path6.resolve(currentDir, "..");
5454
- const nativePath = path6.join(packageRoot, "native", bindingName);
5653
+ const normalizedDir = currentDir.replace(/\\/g, "/");
5654
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
5655
+ const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
5656
+ const nativePath = path7.join(packageRoot, "native", bindingName);
5455
5657
  const require2 = module.createRequire(requireTarget);
5456
5658
  return require2(nativePath);
5457
5659
  }
@@ -5482,11 +5684,20 @@ function createMockNativeBinding() {
5482
5684
  constructor() {
5483
5685
  throw error;
5484
5686
  }
5687
+ serialize() {
5688
+ throw error;
5689
+ }
5690
+ deserialize() {
5691
+ throw error;
5692
+ }
5485
5693
  },
5486
5694
  Database: class {
5487
5695
  constructor() {
5488
5696
  throw error;
5489
5697
  }
5698
+ close() {
5699
+ throw error;
5700
+ }
5490
5701
  }
5491
5702
  };
5492
5703
  }
@@ -5619,7 +5830,7 @@ var MAX_SINGLE_CHUNK_TOKENS = 2e3;
5619
5830
  function estimateTokens(text) {
5620
5831
  return Math.ceil(text.length / CHARS_PER_TOKEN);
5621
5832
  }
5622
- function createEmbeddingText(chunk, filePath) {
5833
+ function getEmbeddingHeaderParts(chunk, filePath) {
5623
5834
  const parts = [];
5624
5835
  const fileName = filePath.split("/").pop() || filePath;
5625
5836
  const dirPath = filePath.split("/").slice(-3, -1).join("/");
@@ -5666,23 +5877,52 @@ function createEmbeddingText(chunk, filePath) {
5666
5877
  if (semanticHints.length > 0) {
5667
5878
  parts.push(`Purpose: ${semanticHints.join(", ")}`);
5668
5879
  }
5669
- parts.push("");
5670
- let content = chunk.content;
5671
- const headerLength = parts.join("\n").length;
5672
- const maxContentChars = MAX_SINGLE_CHUNK_TOKENS * CHARS_PER_TOKEN - headerLength;
5673
- if (content.length > maxContentChars) {
5674
- content = content.slice(0, maxContentChars) + "\n... [truncated]";
5880
+ return parts;
5881
+ }
5882
+ function buildEmbeddingText(headerParts, content, partIndex, partCount) {
5883
+ const parts = [...headerParts];
5884
+ if (partCount && partCount > 1 && partIndex) {
5885
+ parts.push(`Part ${partIndex}/${partCount}`);
5675
5886
  }
5887
+ parts.push("");
5676
5888
  parts.push(content);
5677
5889
  return parts.join("\n");
5678
5890
  }
5679
- function createDynamicBatches(chunks) {
5891
+ function splitOversizedContent(content, maxContentChars) {
5892
+ if (content.length <= maxContentChars) {
5893
+ return [content];
5894
+ }
5895
+ const overlapChars = Math.max(CHARS_PER_TOKEN * 32, Math.min(Math.floor(maxContentChars * 0.15), CHARS_PER_TOKEN * 128));
5896
+ const stepChars = Math.max(1, maxContentChars - overlapChars);
5897
+ const segments = [];
5898
+ for (let start = 0; start < content.length; start += stepChars) {
5899
+ const end = Math.min(content.length, start + maxContentChars);
5900
+ segments.push(content.slice(start, end));
5901
+ if (end >= content.length) {
5902
+ break;
5903
+ }
5904
+ }
5905
+ return segments;
5906
+ }
5907
+ function createEmbeddingTexts(chunk, filePath, maxChunkTokens = MAX_SINGLE_CHUNK_TOKENS) {
5908
+ const headerParts = getEmbeddingHeaderParts(chunk, filePath);
5909
+ const headerLength = buildEmbeddingText(headerParts, "", 1, 9).length;
5910
+ const maxContentChars = Math.max(1, maxChunkTokens * CHARS_PER_TOKEN - headerLength);
5911
+ const segments = splitOversizedContent(chunk.content, maxContentChars);
5912
+ if (segments.length === 1) {
5913
+ return [buildEmbeddingText(headerParts, segments[0])];
5914
+ }
5915
+ return segments.map((segment, index) => buildEmbeddingText(headerParts, segment, index + 1, segments.length));
5916
+ }
5917
+ function createDynamicBatches(chunks, options = {}) {
5680
5918
  const batches = [];
5681
5919
  let currentBatch = [];
5682
5920
  let currentTokens = 0;
5921
+ const maxBatchTokens = Math.max(1, options.maxBatchTokens ?? MAX_BATCH_TOKENS);
5922
+ const maxBatchItems = Math.max(1, options.maxBatchItems ?? Number.MAX_SAFE_INTEGER);
5683
5923
  for (const chunk of chunks) {
5684
- const chunkTokens = estimateTokens(chunk.text);
5685
- if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
5924
+ const chunkTokens = chunk.tokenCount ?? estimateTokens(chunk.text);
5925
+ if (currentBatch.length > 0 && (currentTokens + chunkTokens > maxBatchTokens || currentBatch.length >= maxBatchItems)) {
5686
5926
  batches.push(currentBatch);
5687
5927
  currentBatch = [];
5688
5928
  currentTokens = 0;
@@ -5801,6 +6041,12 @@ var InvertedIndex = class {
5801
6041
  save() {
5802
6042
  this.inner.save();
5803
6043
  }
6044
+ serialize() {
6045
+ return this.inner.serialize();
6046
+ }
6047
+ deserialize(json) {
6048
+ this.inner.deserialize(json);
6049
+ }
5804
6050
  addChunk(chunkId, content) {
5805
6051
  this.inner.addChunk(chunkId, content);
5806
6052
  }
@@ -5827,158 +6073,263 @@ var InvertedIndex = class {
5827
6073
  };
5828
6074
  var Database = class {
5829
6075
  inner;
6076
+ closed = false;
5830
6077
  constructor(dbPath) {
5831
6078
  this.inner = new native.Database(dbPath);
5832
6079
  }
6080
+ throwIfClosed() {
6081
+ if (this.closed) {
6082
+ throw new Error("Database is closed");
6083
+ }
6084
+ }
6085
+ close() {
6086
+ if (this.closed) {
6087
+ return;
6088
+ }
6089
+ if (typeof this.inner.close === "function") {
6090
+ this.inner.close();
6091
+ }
6092
+ this.closed = true;
6093
+ }
5833
6094
  embeddingExists(contentHash) {
6095
+ this.throwIfClosed();
5834
6096
  return this.inner.embeddingExists(contentHash);
5835
6097
  }
5836
6098
  getEmbedding(contentHash) {
6099
+ this.throwIfClosed();
5837
6100
  return this.inner.getEmbedding(contentHash) ?? null;
5838
6101
  }
5839
6102
  upsertEmbedding(contentHash, embedding, chunkText, model) {
6103
+ this.throwIfClosed();
5840
6104
  this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
5841
6105
  }
5842
6106
  upsertEmbeddingsBatch(items) {
6107
+ this.throwIfClosed();
5843
6108
  if (items.length === 0) return;
5844
6109
  this.inner.upsertEmbeddingsBatch(items);
5845
6110
  }
5846
6111
  getMissingEmbeddings(contentHashes) {
6112
+ this.throwIfClosed();
5847
6113
  return this.inner.getMissingEmbeddings(contentHashes);
5848
6114
  }
5849
6115
  upsertChunk(chunk) {
6116
+ this.throwIfClosed();
5850
6117
  this.inner.upsertChunk(chunk);
5851
6118
  }
5852
6119
  upsertChunksBatch(chunks) {
6120
+ this.throwIfClosed();
5853
6121
  if (chunks.length === 0) return;
5854
6122
  this.inner.upsertChunksBatch(chunks);
5855
6123
  }
5856
6124
  getChunk(chunkId) {
6125
+ this.throwIfClosed();
5857
6126
  return this.inner.getChunk(chunkId) ?? null;
5858
6127
  }
5859
6128
  getChunksByFile(filePath) {
6129
+ this.throwIfClosed();
5860
6130
  return this.inner.getChunksByFile(filePath);
5861
6131
  }
5862
6132
  getChunksByName(name) {
6133
+ this.throwIfClosed();
5863
6134
  return this.inner.getChunksByName(name);
5864
6135
  }
5865
6136
  getChunksByNameCi(name) {
6137
+ this.throwIfClosed();
5866
6138
  return this.inner.getChunksByNameCi(name);
5867
6139
  }
5868
6140
  deleteChunksByFile(filePath) {
6141
+ this.throwIfClosed();
5869
6142
  return this.inner.deleteChunksByFile(filePath);
5870
6143
  }
6144
+ deleteChunksByIds(chunkIds) {
6145
+ this.throwIfClosed();
6146
+ if (chunkIds.length === 0) return 0;
6147
+ return this.inner.deleteChunksByIds(chunkIds);
6148
+ }
5871
6149
  addChunksToBranch(branch, chunkIds) {
6150
+ this.throwIfClosed();
5872
6151
  this.inner.addChunksToBranch(branch, chunkIds);
5873
6152
  }
5874
6153
  addChunksToBranchBatch(branch, chunkIds) {
6154
+ this.throwIfClosed();
5875
6155
  if (chunkIds.length === 0) return;
5876
6156
  this.inner.addChunksToBranchBatch(branch, chunkIds);
5877
6157
  }
5878
6158
  clearBranch(branch) {
6159
+ this.throwIfClosed();
5879
6160
  return this.inner.clearBranch(branch);
5880
6161
  }
6162
+ deleteBranchChunksByChunkIds(chunkIds) {
6163
+ this.throwIfClosed();
6164
+ if (chunkIds.length === 0) return 0;
6165
+ return this.inner.deleteBranchChunksByChunkIds(chunkIds);
6166
+ }
6167
+ deleteBranchChunksForBranch(branch, chunkIds) {
6168
+ this.throwIfClosed();
6169
+ if (chunkIds.length === 0) return 0;
6170
+ return this.inner.deleteBranchChunksForBranch(branch, chunkIds);
6171
+ }
5881
6172
  getBranchChunkIds(branch) {
6173
+ this.throwIfClosed();
5882
6174
  return this.inner.getBranchChunkIds(branch);
5883
6175
  }
5884
6176
  getBranchDelta(branch, baseBranch) {
6177
+ this.throwIfClosed();
5885
6178
  return this.inner.getBranchDelta(branch, baseBranch);
5886
6179
  }
6180
+ getReferencedChunkIds(chunkIds) {
6181
+ this.throwIfClosed();
6182
+ if (chunkIds.length === 0) return [];
6183
+ return this.inner.getReferencedChunkIds(chunkIds);
6184
+ }
5887
6185
  chunkExistsOnBranch(branch, chunkId) {
6186
+ this.throwIfClosed();
5888
6187
  return this.inner.chunkExistsOnBranch(branch, chunkId);
5889
6188
  }
5890
6189
  getAllBranches() {
6190
+ this.throwIfClosed();
5891
6191
  return this.inner.getAllBranches();
5892
6192
  }
5893
6193
  getMetadata(key) {
6194
+ this.throwIfClosed();
5894
6195
  return this.inner.getMetadata(key) ?? null;
5895
6196
  }
5896
6197
  setMetadata(key, value) {
6198
+ this.throwIfClosed();
5897
6199
  this.inner.setMetadata(key, value);
5898
6200
  }
5899
6201
  deleteMetadata(key) {
6202
+ this.throwIfClosed();
5900
6203
  return this.inner.deleteMetadata(key);
5901
6204
  }
6205
+ clearAllIndexedData() {
6206
+ this.throwIfClosed();
6207
+ this.inner.clearAllIndexedData();
6208
+ }
6209
+ clearCallEdgeTargetsForSymbols(symbolIds) {
6210
+ this.throwIfClosed();
6211
+ if (symbolIds.length === 0) return 0;
6212
+ return this.inner.clearCallEdgeTargetsForSymbols(symbolIds);
6213
+ }
5902
6214
  gcOrphanEmbeddings() {
6215
+ this.throwIfClosed();
5903
6216
  return this.inner.gcOrphanEmbeddings();
5904
6217
  }
5905
6218
  gcOrphanChunks() {
6219
+ this.throwIfClosed();
5906
6220
  return this.inner.gcOrphanChunks();
5907
6221
  }
5908
6222
  getStats() {
6223
+ this.throwIfClosed();
5909
6224
  return this.inner.getStats();
5910
6225
  }
5911
6226
  // ── Symbol methods ──────────────────────────────────────────────
5912
6227
  upsertSymbol(symbol) {
6228
+ this.throwIfClosed();
5913
6229
  this.inner.upsertSymbol(symbol);
5914
6230
  }
5915
6231
  upsertSymbolsBatch(symbols) {
6232
+ this.throwIfClosed();
5916
6233
  if (symbols.length === 0) return;
5917
6234
  this.inner.upsertSymbolsBatch(symbols);
5918
6235
  }
5919
6236
  getSymbolsByFile(filePath) {
6237
+ this.throwIfClosed();
5920
6238
  return this.inner.getSymbolsByFile(filePath);
5921
6239
  }
5922
6240
  getSymbolByName(name, filePath) {
6241
+ this.throwIfClosed();
5923
6242
  return this.inner.getSymbolByName(name, filePath) ?? null;
5924
6243
  }
5925
6244
  getSymbolsByName(name) {
6245
+ this.throwIfClosed();
5926
6246
  return this.inner.getSymbolsByName(name);
5927
6247
  }
5928
6248
  getSymbolsByNameCi(name) {
6249
+ this.throwIfClosed();
5929
6250
  return this.inner.getSymbolsByNameCi(name);
5930
6251
  }
5931
6252
  deleteSymbolsByFile(filePath) {
6253
+ this.throwIfClosed();
5932
6254
  return this.inner.deleteSymbolsByFile(filePath);
5933
6255
  }
5934
6256
  // ── Call Edge methods ────────────────────────────────────────────
5935
6257
  upsertCallEdge(edge) {
6258
+ this.throwIfClosed();
5936
6259
  this.inner.upsertCallEdge(edge);
5937
6260
  }
5938
6261
  upsertCallEdgesBatch(edges) {
6262
+ this.throwIfClosed();
5939
6263
  if (edges.length === 0) return;
5940
6264
  this.inner.upsertCallEdgesBatch(edges);
5941
6265
  }
5942
6266
  getCallers(targetName, branch) {
6267
+ this.throwIfClosed();
5943
6268
  return this.inner.getCallers(targetName, branch);
5944
6269
  }
5945
6270
  getCallersWithContext(targetName, branch) {
6271
+ this.throwIfClosed();
5946
6272
  return this.inner.getCallersWithContext(targetName, branch);
5947
6273
  }
5948
6274
  getCallees(symbolId, branch) {
6275
+ this.throwIfClosed();
5949
6276
  return this.inner.getCallees(symbolId, branch);
5950
6277
  }
5951
6278
  deleteCallEdgesByFile(filePath) {
6279
+ this.throwIfClosed();
5952
6280
  return this.inner.deleteCallEdgesByFile(filePath);
5953
6281
  }
5954
6282
  resolveCallEdge(edgeId, toSymbolId) {
6283
+ this.throwIfClosed();
5955
6284
  this.inner.resolveCallEdge(edgeId, toSymbolId);
5956
6285
  }
5957
6286
  // ── Branch Symbol methods ────────────────────────────────────────
5958
6287
  addSymbolsToBranch(branch, symbolIds) {
6288
+ this.throwIfClosed();
5959
6289
  this.inner.addSymbolsToBranch(branch, symbolIds);
5960
6290
  }
5961
6291
  addSymbolsToBranchBatch(branch, symbolIds) {
6292
+ this.throwIfClosed();
5962
6293
  if (symbolIds.length === 0) return;
5963
6294
  this.inner.addSymbolsToBranchBatch(branch, symbolIds);
5964
6295
  }
5965
6296
  getBranchSymbolIds(branch) {
6297
+ this.throwIfClosed();
5966
6298
  return this.inner.getBranchSymbolIds(branch);
5967
6299
  }
5968
6300
  clearBranchSymbols(branch) {
6301
+ this.throwIfClosed();
5969
6302
  return this.inner.clearBranchSymbols(branch);
5970
6303
  }
6304
+ getReferencedSymbolIds(symbolIds) {
6305
+ this.throwIfClosed();
6306
+ if (symbolIds.length === 0) return [];
6307
+ return this.inner.getReferencedSymbolIds(symbolIds);
6308
+ }
6309
+ deleteBranchSymbolsBySymbolIds(symbolIds) {
6310
+ this.throwIfClosed();
6311
+ if (symbolIds.length === 0) return 0;
6312
+ return this.inner.deleteBranchSymbolsBySymbolIds(symbolIds);
6313
+ }
6314
+ deleteBranchSymbolsForBranch(branch, symbolIds) {
6315
+ this.throwIfClosed();
6316
+ if (symbolIds.length === 0) return 0;
6317
+ return this.inner.deleteBranchSymbolsForBranch(branch, symbolIds);
6318
+ }
5971
6319
  // ── GC methods for symbols/edges ─────────────────────────────────
5972
6320
  gcOrphanSymbols() {
6321
+ this.throwIfClosed();
5973
6322
  return this.inner.gcOrphanSymbols();
5974
6323
  }
5975
6324
  gcOrphanCallEdges() {
6325
+ this.throwIfClosed();
5976
6326
  return this.inner.gcOrphanCallEdges();
5977
6327
  }
5978
6328
  };
5979
6329
 
5980
6330
  // src/indexer/index.ts
5981
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
6331
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig"]);
6332
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5982
6333
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5983
6334
  "function_declaration",
5984
6335
  "function",
@@ -6000,7 +6351,11 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
6000
6351
  "enum_item",
6001
6352
  "trait_item",
6002
6353
  "mod_item",
6003
- "trait_declaration"
6354
+ "trait_declaration",
6355
+ "trigger_declaration",
6356
+ "test_declaration",
6357
+ "struct_declaration",
6358
+ "union_declaration"
6004
6359
  ]);
6005
6360
  function float32ArrayToBuffer(arr) {
6006
6361
  const float32 = new Float32Array(arr);
@@ -6025,12 +6380,192 @@ function isRateLimitError(error) {
6025
6380
  const message = getErrorMessage(error);
6026
6381
  return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
6027
6382
  }
6383
+ function getSafeEmbeddingChunkTokenLimit(provider) {
6384
+ const providerMaxTokens = provider.modelInfo.maxTokens;
6385
+ const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
6386
+ return Math.min(2e3, maxChunkTokens);
6387
+ }
6388
+ function getDynamicBatchOptions(provider) {
6389
+ if (provider.provider === "ollama") {
6390
+ return {
6391
+ maxBatchTokens: provider.modelInfo.maxTokens,
6392
+ maxBatchItems: 1
6393
+ };
6394
+ }
6395
+ return {};
6396
+ }
6397
+ function isSqliteCorruptionError(error) {
6398
+ const message = getErrorMessage(error).toLowerCase();
6399
+ 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");
6400
+ }
6401
+ var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
6028
6402
  var INDEX_METADATA_VERSION = "1";
6403
+ var EMBEDDING_STRATEGY_VERSION = "2";
6029
6404
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
6405
+ var RANK_HYBRID_CACHE_LIMIT = 256;
6406
+ function createPendingChunkStorageText(texts) {
6407
+ const primaryText = texts[0]?.text ?? "";
6408
+ if (texts.length <= 1) {
6409
+ return primaryText;
6410
+ }
6411
+ return `${primaryText}
6412
+
6413
+ ... [split into ${texts.length} parts for embedding]`;
6414
+ }
6415
+ function normalizePendingChunk(rawChunk, maxChunkTokens) {
6416
+ if (!rawChunk || typeof rawChunk !== "object") {
6417
+ return null;
6418
+ }
6419
+ const chunk = rawChunk;
6420
+ if (typeof chunk.id !== "string" || typeof chunk.contentHash !== "string" || !chunk.metadata || typeof chunk.metadata !== "object") {
6421
+ return null;
6422
+ }
6423
+ const texts = Array.isArray(chunk.texts) ? chunk.texts.map((entry) => {
6424
+ if (!entry || typeof entry.text !== "string") {
6425
+ return null;
6426
+ }
6427
+ return {
6428
+ text: entry.text,
6429
+ tokenCount: typeof entry.tokenCount === "number" && Number.isFinite(entry.tokenCount) ? entry.tokenCount : estimateTokens(entry.text)
6430
+ };
6431
+ }).filter((entry) => entry !== null) : [];
6432
+ if (texts.length === 0 && typeof chunk.text === "string") {
6433
+ if (typeof chunk.content === "string" && chunk.content.length > 0 && chunk.metadata && typeof chunk.metadata === "object") {
6434
+ const metadata = chunk.metadata;
6435
+ const rebuiltChunk = {
6436
+ content: chunk.content,
6437
+ startLine: typeof metadata.startLine === "number" ? metadata.startLine : 1,
6438
+ endLine: typeof metadata.endLine === "number" ? metadata.endLine : 1,
6439
+ chunkType: typeof metadata.chunkType === "string" ? metadata.chunkType : "other",
6440
+ name: typeof metadata.name === "string" ? metadata.name : void 0,
6441
+ language: typeof metadata.language === "string" ? metadata.language : "text"
6442
+ };
6443
+ const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
6444
+ texts.push(
6445
+ ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text) => ({
6446
+ text,
6447
+ tokenCount: estimateTokens(text)
6448
+ }))
6449
+ );
6450
+ } else {
6451
+ texts.push({
6452
+ text: chunk.text,
6453
+ tokenCount: estimateTokens(chunk.text)
6454
+ });
6455
+ }
6456
+ }
6457
+ if (texts.length === 0) {
6458
+ return null;
6459
+ }
6460
+ return {
6461
+ id: chunk.id,
6462
+ texts,
6463
+ storageText: typeof chunk.storageText === "string" ? chunk.storageText : createPendingChunkStorageText(texts),
6464
+ content: typeof chunk.content === "string" ? chunk.content : "",
6465
+ contentHash: chunk.contentHash,
6466
+ metadata: chunk.metadata
6467
+ };
6468
+ }
6469
+ function getPendingChunkFilePath(rawChunk) {
6470
+ if (!rawChunk || typeof rawChunk !== "object") {
6471
+ return null;
6472
+ }
6473
+ const chunk = rawChunk;
6474
+ if (!chunk.metadata || typeof chunk.metadata !== "object") {
6475
+ return null;
6476
+ }
6477
+ const metadata = chunk.metadata;
6478
+ return typeof metadata.filePath === "string" ? metadata.filePath : null;
6479
+ }
6480
+ function normalizeFailedBatch(batch, maxChunkTokens) {
6481
+ const chunks = batch.chunks.map((chunk) => normalizePendingChunk(chunk, maxChunkTokens)).filter((chunk) => chunk !== null);
6482
+ if (chunks.length === 0) {
6483
+ return null;
6484
+ }
6485
+ return {
6486
+ chunks,
6487
+ error: batch.error,
6488
+ attemptCount: batch.attemptCount,
6489
+ lastAttempt: batch.lastAttempt
6490
+ };
6491
+ }
6492
+ function createPendingEmbeddingRequests(chunks) {
6493
+ return chunks.flatMap(
6494
+ (chunk) => chunk.texts.map((textPart, partIndex) => ({
6495
+ chunk,
6496
+ partIndex,
6497
+ text: textPart.text,
6498
+ tokenCount: textPart.tokenCount
6499
+ }))
6500
+ );
6501
+ }
6502
+ function createPendingEmbeddingRequestBatches(chunks, options = {}) {
6503
+ return createDynamicBatches(createPendingEmbeddingRequests(chunks), options);
6504
+ }
6505
+ function getUniquePendingChunksFromRequests(requests) {
6506
+ const uniqueChunks = /* @__PURE__ */ new Map();
6507
+ for (const request of requests) {
6508
+ uniqueChunks.set(request.chunk.id, request.chunk);
6509
+ }
6510
+ return Array.from(uniqueChunks.values());
6511
+ }
6512
+ function coalesceFailedBatches(batches) {
6513
+ const grouped = /* @__PURE__ */ new Map();
6514
+ for (const batch of batches) {
6515
+ const key = `${batch.attemptCount}:${batch.lastAttempt}:${batch.error}`;
6516
+ const existing = grouped.get(key);
6517
+ if (!existing) {
6518
+ grouped.set(key, {
6519
+ ...batch,
6520
+ chunks: [...batch.chunks]
6521
+ });
6522
+ continue;
6523
+ }
6524
+ existing.chunks.push(...batch.chunks);
6525
+ }
6526
+ return Array.from(grouped.values());
6527
+ }
6528
+ function poolEmbeddingVectors(vectors, weights) {
6529
+ const firstVector = vectors[0];
6530
+ if (!firstVector) {
6531
+ return [];
6532
+ }
6533
+ const pooled = new Array(firstVector.length).fill(0);
6534
+ let totalWeight = 0;
6535
+ for (let index = 0; index < vectors.length; index++) {
6536
+ const vector = vectors[index];
6537
+ const weight = Math.max(1, weights[index] ?? 1);
6538
+ totalWeight += weight;
6539
+ for (let dimension = 0; dimension < vector.length; dimension++) {
6540
+ pooled[dimension] += vector[dimension] * weight;
6541
+ }
6542
+ }
6543
+ if (totalWeight === 0) {
6544
+ return firstVector;
6545
+ }
6546
+ return pooled.map((value) => value / totalWeight);
6547
+ }
6548
+ function hasAllEmbeddingParts(parts, expectedPartCount) {
6549
+ if (parts.length !== expectedPartCount) {
6550
+ return false;
6551
+ }
6552
+ for (let index = 0; index < expectedPartCount; index++) {
6553
+ if (parts[index] === void 0) {
6554
+ return false;
6555
+ }
6556
+ }
6557
+ return true;
6558
+ }
6559
+ function isPathWithinRoot(filePath, rootPath) {
6560
+ const normalizedFilePath = path8.resolve(filePath);
6561
+ const normalizedRoot = path8.resolve(rootPath);
6562
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path8.sep}`);
6563
+ }
6030
6564
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
6031
6565
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
6032
6566
  var rankingPathTokenCache = /* @__PURE__ */ new Map();
6033
6567
  var rankingTextTokenCache = /* @__PURE__ */ new Map();
6568
+ var rankHybridResultsCache = /* @__PURE__ */ new WeakMap();
6034
6569
  var STOPWORDS = /* @__PURE__ */ new Set([
6035
6570
  "the",
6036
6571
  "and",
@@ -6246,7 +6781,16 @@ function classifyExternalRerankBand(candidate, preferSourcePaths, docIntent) {
6246
6781
  function classifyQueryIntent(tokens) {
6247
6782
  const sourceIntentHits = tokens.filter((t) => SOURCE_INTENT_HINTS.has(t)).length;
6248
6783
  const docTestIntentHits = tokens.filter((t) => DOC_TEST_INTENT_HINTS.has(t)).length;
6249
- return sourceIntentHits >= docTestIntentHits ? "source" : "doc_test";
6784
+ if (sourceIntentHits === 0 && docTestIntentHits === 0) {
6785
+ return "neutral";
6786
+ }
6787
+ if (sourceIntentHits > docTestIntentHits) {
6788
+ return "source";
6789
+ }
6790
+ if (docTestIntentHits > sourceIntentHits) {
6791
+ return "doc_test";
6792
+ }
6793
+ return "neutral";
6250
6794
  }
6251
6795
  function classifyQueryIntentRaw(query) {
6252
6796
  const lowerQuery = query.toLowerCase();
@@ -6268,7 +6812,7 @@ function classifyQueryIntentRaw(query) {
6268
6812
  if (docTestRawHits > sourceRawHits) {
6269
6813
  return "doc_test";
6270
6814
  }
6271
- if (sourceRawHits > docTestRawHits) {
6815
+ if (sourceRawHits > docTestRawHits && sourceRawHits > 0) {
6272
6816
  return "source";
6273
6817
  }
6274
6818
  const hasWhereIsPattern = /\bwhere\s+is\b/.test(lowerQuery);
@@ -6535,9 +7079,8 @@ function rerankResults(query, candidates, rerankTopN, options) {
6535
7079
  return candidates;
6536
7080
  }
6537
7081
  const queryTokenList = Array.from(queryTokens);
6538
- const intent = classifyQueryIntentRaw(query);
6539
7082
  const docIntent = classifyDocIntent(queryTokenList);
6540
- const preferSourcePaths = options?.prioritizeSourcePaths ?? intent === "source";
7083
+ const preferSourcePaths = options?.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
6541
7084
  const identifierHints = extractIdentifierHints(query);
6542
7085
  const head = candidates.slice(0, topN).map((candidate, idx) => {
6543
7086
  const pathTokens = splitPathTokens(candidate.metadata.filePath);
@@ -6687,13 +7230,38 @@ function buildDiversityKey(metadata) {
6687
7230
  return normalizedPath;
6688
7231
  }
6689
7232
  function rankHybridResults(query, semanticResults, keywordResults, options) {
7233
+ const prioritizeSourcePaths = options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
7234
+ const cacheKey = `${query}${options.fusionStrategy}|${options.rrfK}|${options.hybridWeight}|${options.rerankTopN}|${options.limit}|${prioritizeSourcePaths ? 1 : 0}`;
7235
+ let byKeyword = rankHybridResultsCache.get(semanticResults);
7236
+ if (!byKeyword) {
7237
+ byKeyword = /* @__PURE__ */ new WeakMap();
7238
+ rankHybridResultsCache.set(semanticResults, byKeyword);
7239
+ }
7240
+ let bucket = byKeyword.get(keywordResults);
7241
+ if (!bucket) {
7242
+ bucket = /* @__PURE__ */ new Map();
7243
+ byKeyword.set(keywordResults, bucket);
7244
+ } else {
7245
+ const cached = bucket.get(cacheKey);
7246
+ if (cached) {
7247
+ return cached;
7248
+ }
7249
+ }
6690
7250
  const overfetchLimit = Math.max(options.limit * 4, options.limit);
6691
7251
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
6692
7252
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
6693
7253
  const rerankPool = fused.slice(0, rerankPoolLimit);
6694
- return rerankResults(query, rerankPool, options.rerankTopN, {
6695
- prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
7254
+ const ranked = rerankResults(query, rerankPool, options.rerankTopN, {
7255
+ prioritizeSourcePaths
6696
7256
  });
7257
+ if (bucket.size >= RANK_HYBRID_CACHE_LIMIT) {
7258
+ const oldest = bucket.keys().next().value;
7259
+ if (oldest !== void 0) {
7260
+ bucket.delete(oldest);
7261
+ }
7262
+ }
7263
+ bucket.set(cacheKey, ranked);
7264
+ return ranked;
6697
7265
  }
6698
7266
  function rankSemanticOnlyResults(query, semanticResults, options) {
6699
7267
  const overfetchLimit = Math.max(options.limit * 4, options.limit);
@@ -6990,6 +7558,21 @@ function mergeTieredResults(symbolLane, hybridLane, limit) {
6990
7558
  }
6991
7559
  return out;
6992
7560
  }
7561
+ function matchesSearchFilters(candidate, options, minScore) {
7562
+ if (candidate.score < minScore) return false;
7563
+ if (options?.fileType) {
7564
+ const ext = candidate.metadata.filePath.split(".").pop()?.toLowerCase();
7565
+ if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
7566
+ }
7567
+ if (options?.directory) {
7568
+ const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
7569
+ if (!candidate.metadata.filePath.includes(`/${normalizedDir}/`) && !candidate.metadata.filePath.includes(`${normalizedDir}/`)) return false;
7570
+ }
7571
+ if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
7572
+ return false;
7573
+ }
7574
+ return true;
7575
+ }
6993
7576
  function unionCandidates(semanticCandidates, keywordCandidates) {
6994
7577
  const byId = /* @__PURE__ */ new Map();
6995
7578
  for (const candidate of semanticCandidates) {
@@ -7029,21 +7612,17 @@ var Indexer = class {
7029
7612
  this.projectRoot = projectRoot;
7030
7613
  this.config = config;
7031
7614
  this.indexPath = this.getIndexPath();
7032
- this.fileHashCachePath = path7.join(this.indexPath, "file-hashes.json");
7033
- this.failedBatchesPath = path7.join(this.indexPath, "failed-batches.json");
7034
- this.indexingLockPath = path7.join(this.indexPath, "indexing.lock");
7615
+ this.fileHashCachePath = path8.join(this.indexPath, "file-hashes.json");
7616
+ this.failedBatchesPath = path8.join(this.indexPath, "failed-batches.json");
7617
+ this.indexingLockPath = path8.join(this.indexPath, "indexing.lock");
7035
7618
  this.logger = initializeLogger(config.debug);
7036
7619
  }
7037
7620
  getIndexPath() {
7038
- if (this.config.scope === "global") {
7039
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
7040
- return path7.join(homeDir, ".opencode", "global-index");
7041
- }
7042
- return path7.join(this.projectRoot, ".opencode", "index");
7621
+ return resolveProjectIndexPath(this.projectRoot, this.config.scope);
7043
7622
  }
7044
7623
  loadFileHashCache() {
7045
7624
  try {
7046
- if (existsSync5(this.fileHashCachePath)) {
7625
+ if (existsSync6(this.fileHashCachePath)) {
7047
7626
  const data = readFileSync5(this.fileHashCachePath, "utf-8");
7048
7627
  const parsed = JSON.parse(data);
7049
7628
  this.fileHashCache = new Map(Object.entries(parsed));
@@ -7061,95 +7640,454 @@ var Indexer = class {
7061
7640
  }
7062
7641
  atomicWriteSync(targetPath, data) {
7063
7642
  const tempPath = `${targetPath}.tmp`;
7064
- writeFileSync(tempPath, data);
7643
+ writeFileSync2(tempPath, data);
7065
7644
  renameSync(tempPath, targetPath);
7066
7645
  }
7067
- checkForInterruptedIndexing() {
7068
- return existsSync5(this.indexingLockPath);
7646
+ getScopedRoots() {
7647
+ const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7648
+ for (const kbRoot of this.config.knowledgeBases) {
7649
+ roots.add(path8.resolve(this.projectRoot, kbRoot));
7650
+ }
7651
+ return Array.from(roots);
7069
7652
  }
7070
- acquireIndexingLock() {
7071
- const lockData = {
7072
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7073
- pid: process.pid
7074
- };
7075
- writeFileSync(this.indexingLockPath, JSON.stringify(lockData));
7653
+ getBranchCatalogKey() {
7654
+ const branchName = this.currentBranch || "default";
7655
+ if (this.config.scope !== "global") {
7656
+ return branchName;
7657
+ }
7658
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7659
+ return `${projectHash}:${branchName}`;
7076
7660
  }
7077
- releaseIndexingLock() {
7078
- if (existsSync5(this.indexingLockPath)) {
7079
- unlinkSync(this.indexingLockPath);
7661
+ getLegacyBranchCatalogKey() {
7662
+ return this.currentBranch || "default";
7663
+ }
7664
+ getLegacyMigrationMetadataKey() {
7665
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7666
+ return `index.globalBranchMigration.${projectHash}`;
7667
+ }
7668
+ getProjectEmbeddingStrategyMetadataKey() {
7669
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7670
+ return `index.embeddingStrategyVersion.${projectHash}`;
7671
+ }
7672
+ getProjectForceReembedMetadataKey() {
7673
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7674
+ return `index.forceReembed.${projectHash}`;
7675
+ }
7676
+ hasProjectForceReembedPending() {
7677
+ return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7678
+ }
7679
+ hasScopedIndexedData() {
7680
+ if (!this.store || this.config.scope !== "global") {
7681
+ return false;
7682
+ }
7683
+ if (this.hasProjectForceReembedPending()) {
7684
+ return false;
7685
+ }
7686
+ const roots = this.getScopedRoots();
7687
+ if (Array.from(this.fileHashCache.keys()).some((filePath) => this.isFileInCurrentScope(filePath, roots))) {
7688
+ return true;
7689
+ }
7690
+ if (this.loadSerializedFailedBatches().some(
7691
+ (batch) => batch.chunks.some((chunk) => {
7692
+ const filePath = getPendingChunkFilePath(chunk);
7693
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7694
+ })
7695
+ )) {
7696
+ return true;
7697
+ }
7698
+ if (!this.database) {
7699
+ return false;
7700
+ }
7701
+ if (this.getBranchCatalogKeys().some((branchKey) => {
7702
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7703
+ if (branchChunkIds.length > 0) {
7704
+ return true;
7705
+ }
7706
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7707
+ })) {
7708
+ return true;
7080
7709
  }
7710
+ const hasAnyBranchRows = this.database.getAllBranches().some((branchKey) => {
7711
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7712
+ if (branchChunkIds.length > 0) {
7713
+ return true;
7714
+ }
7715
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7716
+ });
7717
+ if (hasAnyBranchRows) {
7718
+ return false;
7719
+ }
7720
+ return this.store.getAllMetadata().some(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7081
7721
  }
7082
- async recoverFromInterruptedIndexing() {
7083
- this.logger.warn("Detected interrupted indexing session, recovering...");
7084
- if (existsSync5(this.fileHashCachePath)) {
7085
- unlinkSync(this.fileHashCachePath);
7722
+ loadStoredEmbeddingStrategyVersion() {
7723
+ if (!this.database) {
7724
+ return null;
7086
7725
  }
7087
- await this.healthCheck();
7088
- this.releaseIndexingLock();
7089
- this.logger.info("Recovery complete, next index will re-process all files");
7726
+ if (this.hasProjectForceReembedPending()) {
7727
+ return null;
7728
+ }
7729
+ if (this.config.scope !== "global") {
7730
+ return this.database.getMetadata("index.embeddingStrategyVersion") ?? "1";
7731
+ }
7732
+ const projectVersion = this.database.getMetadata(this.getProjectEmbeddingStrategyMetadataKey());
7733
+ if (projectVersion) {
7734
+ return projectVersion;
7735
+ }
7736
+ const legacySharedVersion = this.database.getMetadata("index.embeddingStrategyVersion");
7737
+ if (legacySharedVersion && this.hasScopedIndexedData()) {
7738
+ return legacySharedVersion;
7739
+ }
7740
+ return null;
7090
7741
  }
7091
- loadFailedBatches() {
7092
- try {
7093
- if (existsSync5(this.failedBatchesPath)) {
7094
- const data = readFileSync5(this.failedBatchesPath, "utf-8");
7095
- return JSON.parse(data);
7742
+ getBranchCatalogKeys() {
7743
+ const primary = this.getBranchCatalogKey();
7744
+ if (this.config.scope !== "global") {
7745
+ return [primary];
7746
+ }
7747
+ if (this.database?.getMetadata(this.getLegacyMigrationMetadataKey()) === "done") {
7748
+ return [primary];
7749
+ }
7750
+ const legacy = this.getLegacyBranchCatalogKey();
7751
+ return primary === legacy ? [primary] : [primary, legacy];
7752
+ }
7753
+ getBranchCatalogCleanupKeys() {
7754
+ const primary = this.getBranchCatalogKey();
7755
+ if (this.config.scope !== "global") {
7756
+ return [primary];
7757
+ }
7758
+ const legacy = this.getLegacyBranchCatalogKey();
7759
+ return primary === legacy ? [primary] : [primary, legacy];
7760
+ }
7761
+ getProjectLocalScopedOwnershipIds(roots) {
7762
+ const chunkIds = /* @__PURE__ */ new Set();
7763
+ const symbolIds = /* @__PURE__ */ new Set();
7764
+ if (!this.database) {
7765
+ return { chunkIds, symbolIds };
7766
+ }
7767
+ const projectRootPath = path8.resolve(this.projectRoot);
7768
+ const projectLocalFilePaths = /* @__PURE__ */ new Set([
7769
+ ...Array.from(this.fileHashCache.keys()).filter(
7770
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7771
+ ),
7772
+ ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
7773
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7774
+ )
7775
+ ]);
7776
+ for (const filePath of projectLocalFilePaths) {
7777
+ for (const chunk of this.database.getChunksByFile(filePath)) {
7778
+ chunkIds.add(chunk.chunkId);
7779
+ }
7780
+ for (const symbol of this.database.getSymbolsByFile(filePath)) {
7781
+ symbolIds.add(symbol.id);
7096
7782
  }
7097
- } catch {
7098
- return [];
7099
7783
  }
7100
- return [];
7784
+ return { chunkIds, symbolIds };
7101
7785
  }
7102
- saveFailedBatches(batches) {
7103
- if (batches.length === 0) {
7104
- if (existsSync5(this.failedBatchesPath)) {
7105
- fsPromises2.unlink(this.failedBatchesPath).catch(() => {
7106
- });
7786
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
7787
+ if (this.config.scope !== "global") {
7788
+ return this.getBranchCatalogCleanupKeys();
7789
+ }
7790
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7791
+ const keys = /* @__PURE__ */ new Set();
7792
+ const projectChunkIdSet = new Set(projectChunkIds);
7793
+ const projectSymbolIdSet = new Set(projectSymbolIds);
7794
+ for (const branchKey of this.database?.getAllBranches() ?? []) {
7795
+ if (branchKey.startsWith(`${projectHash}:`)) {
7796
+ keys.add(branchKey);
7797
+ continue;
7107
7798
  }
7108
- return;
7799
+ if (branchKey.includes(":")) {
7800
+ continue;
7801
+ }
7802
+ const referencesProjectChunks = this.database?.getBranchChunkIds(branchKey).some((chunkId) => projectChunkIdSet.has(chunkId)) ?? false;
7803
+ const referencesProjectSymbols = this.database?.getBranchSymbolIds(branchKey).some((symbolId) => projectSymbolIdSet.has(symbolId)) ?? false;
7804
+ if (referencesProjectChunks || referencesProjectSymbols) {
7805
+ keys.add(branchKey);
7806
+ }
7807
+ }
7808
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
7809
+ keys.add(branchKey);
7109
7810
  }
7110
- writeFileSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7811
+ return Array.from(keys);
7111
7812
  }
7112
- addFailedBatch(batch, error) {
7113
- const existing = this.loadFailedBatches();
7114
- existing.push({
7115
- chunks: batch,
7116
- error,
7117
- attemptCount: 1,
7118
- lastAttempt: (/* @__PURE__ */ new Date()).toISOString()
7119
- });
7120
- this.saveFailedBatches(existing);
7813
+ isFileInCurrentScope(filePath, roots) {
7814
+ return roots.some((root) => isPathWithinRoot(filePath, root));
7121
7815
  }
7122
- getProviderRateLimits(provider) {
7123
- switch (provider) {
7124
- case "github-copilot":
7125
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
7126
- case "openai":
7127
- return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
7128
- case "google":
7129
- return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
7130
- case "ollama":
7131
- return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
7132
- case "custom": {
7133
- const customConfig = this.config.customProvider;
7134
- return {
7135
- concurrency: customConfig?.concurrency ?? 3,
7136
- intervalMs: customConfig?.requestIntervalMs ?? 1e3,
7137
- minRetryMs: 1e3,
7138
- maxRetryMs: 3e4
7139
- };
7816
+ clearScopedFileHashCache(roots) {
7817
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7818
+ if (this.isFileInCurrentScope(filePath, roots)) {
7819
+ this.fileHashCache.delete(filePath);
7140
7820
  }
7141
- default:
7142
- return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
7143
7821
  }
7822
+ this.saveFileHashCache();
7144
7823
  }
7145
- async rerankCandidatesWithApi(query, candidates, options) {
7146
- const reranker = this.config.reranker;
7147
- if (!reranker || !reranker.enabled || candidates.length <= 1) {
7148
- return candidates;
7824
+ replaceScopedFileHashCache(currentFileHashes, roots) {
7825
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7826
+ if (this.isFileInCurrentScope(filePath, roots)) {
7827
+ this.fileHashCache.delete(filePath);
7828
+ }
7149
7829
  }
7150
- const queryTokens = Array.from(tokenizeTextForRanking(query));
7151
- const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
7152
- const docIntent = classifyDocIntent(queryTokens) === "docs";
7830
+ for (const [filePath, hash] of currentFileHashes) {
7831
+ this.fileHashCache.set(filePath, hash);
7832
+ }
7833
+ this.saveFileHashCache();
7834
+ }
7835
+ partitionFailedBatches(roots, maxChunkTokens) {
7836
+ const scoped = [];
7837
+ const retained = [];
7838
+ for (const batch of this.loadSerializedFailedBatches()) {
7839
+ const scopedChunks = batch.chunks.filter((chunk) => {
7840
+ const filePath = getPendingChunkFilePath(chunk);
7841
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7842
+ });
7843
+ const retainedChunks = batch.chunks.filter((chunk) => {
7844
+ const filePath = getPendingChunkFilePath(chunk);
7845
+ return filePath === null || !this.isFileInCurrentScope(filePath, roots);
7846
+ });
7847
+ if (scopedChunks.length > 0) {
7848
+ const normalizedBatch = normalizeFailedBatch({ ...batch, chunks: scopedChunks }, maxChunkTokens);
7849
+ if (normalizedBatch) {
7850
+ scoped.push(normalizedBatch);
7851
+ }
7852
+ }
7853
+ if (retainedChunks.length > 0) {
7854
+ retained.push({ ...batch, chunks: retainedChunks });
7855
+ }
7856
+ }
7857
+ return { scoped, retained };
7858
+ }
7859
+ clearScopedFailedBatches(roots) {
7860
+ const { retained: retainedBatches } = this.partitionFailedBatches(roots);
7861
+ this.saveFailedBatches(retainedBatches);
7862
+ }
7863
+ hasForeignScopedFileHashData(roots) {
7864
+ return Array.from(this.fileHashCache.keys()).some((filePath) => !this.isFileInCurrentScope(filePath, roots));
7865
+ }
7866
+ hasForeignScopedFailedBatches(roots) {
7867
+ const { retained } = this.partitionFailedBatches(roots);
7868
+ return retained.length > 0;
7869
+ }
7870
+ hasForeignScopedBranchData() {
7871
+ if (!this.database || this.config.scope !== "global") {
7872
+ return false;
7873
+ }
7874
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7875
+ const roots = this.getScopedRoots();
7876
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7877
+ return this.database.getAllBranches().some(
7878
+ (branchKey) => {
7879
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7880
+ const branchSymbolIds = this.database.getBranchSymbolIds(branchKey);
7881
+ const hasBranchData = branchChunkIds.length > 0 || branchSymbolIds.length > 0;
7882
+ if (!hasBranchData) {
7883
+ return false;
7884
+ }
7885
+ if (branchKey.startsWith(`${projectHash}:`)) {
7886
+ return false;
7887
+ }
7888
+ if (!branchKey.includes(":")) {
7889
+ const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
7890
+ const referencesCurrentProjectSymbols = branchSymbolIds.some((symbolId) => projectLocalSymbolIds.has(symbolId));
7891
+ return !(referencesCurrentProjectChunks || referencesCurrentProjectSymbols);
7892
+ }
7893
+ return true;
7894
+ }
7895
+ );
7896
+ }
7897
+ saveScopedFailedBatches(batches, roots) {
7898
+ const { retained } = this.partitionFailedBatches(roots);
7899
+ this.saveFailedBatches([...retained, ...batches]);
7900
+ }
7901
+ clearSharedIndexProjectData(store, invertedIndex, database, roots) {
7902
+ const allMetadata = store.getAllMetadata();
7903
+ const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7904
+ const filePaths = /* @__PURE__ */ new Set([
7905
+ ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7906
+ ...scopedEntries.map(({ metadata }) => metadata.filePath)
7907
+ ]);
7908
+ const projectRootPath = path8.resolve(this.projectRoot);
7909
+ const projectLocalFilePaths = new Set(
7910
+ Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7911
+ );
7912
+ const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
7913
+ for (const filePath of filePaths) {
7914
+ for (const chunk of database.getChunksByFile(filePath)) {
7915
+ removedChunkIds.add(chunk.chunkId);
7916
+ }
7917
+ }
7918
+ const removedChunkIdList = Array.from(removedChunkIds);
7919
+ const projectLocalChunkIds = new Set(
7920
+ scopedEntries.filter(({ metadata }) => isPathWithinRoot(metadata.filePath, projectRootPath)).map(({ key }) => key)
7921
+ );
7922
+ for (const filePath of projectLocalFilePaths) {
7923
+ for (const chunk of database.getChunksByFile(filePath)) {
7924
+ projectLocalChunkIds.add(chunk.chunkId);
7925
+ }
7926
+ }
7927
+ const symbolIds = [];
7928
+ const projectLocalSymbolIds = /* @__PURE__ */ new Set();
7929
+ for (const filePath of filePaths) {
7930
+ for (const symbol of database.getSymbolsByFile(filePath)) {
7931
+ symbolIds.push(symbol.id);
7932
+ if (projectLocalFilePaths.has(filePath)) {
7933
+ projectLocalSymbolIds.add(symbol.id);
7934
+ }
7935
+ }
7936
+ }
7937
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7938
+ database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
7939
+ }
7940
+ const sharedChunkIds = new Set(database.getReferencedChunkIds(removedChunkIdList));
7941
+ const removableChunkIds = removedChunkIdList.filter((chunkId) => !sharedChunkIds.has(chunkId));
7942
+ if (removableChunkIds.length > 0) {
7943
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removableChunkIds);
7944
+ for (const chunkId of removableChunkIds) {
7945
+ invertedIndex.removeChunk(chunkId);
7946
+ }
7947
+ }
7948
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7949
+ database.deleteBranchSymbolsForBranch(branchKey, symbolIds);
7950
+ }
7951
+ const sharedSymbolIds = new Set(database.getReferencedSymbolIds(symbolIds));
7952
+ const removableSymbolIds = symbolIds.filter((symbolId) => !sharedSymbolIds.has(symbolId));
7953
+ database.clearCallEdgeTargetsForSymbols(removableSymbolIds);
7954
+ for (const filePath of filePaths) {
7955
+ const fileChunkIds = database.getChunksByFile(filePath).map((chunk) => chunk.chunkId);
7956
+ const fileSymbols = database.getSymbolsByFile(filePath);
7957
+ if (fileChunkIds.every((chunkId) => !sharedChunkIds.has(chunkId))) {
7958
+ database.deleteChunksByFile(filePath);
7959
+ }
7960
+ if (fileSymbols.every((symbol) => !sharedSymbolIds.has(symbol.id))) {
7961
+ database.deleteCallEdgesByFile(filePath);
7962
+ database.deleteSymbolsByFile(filePath);
7963
+ }
7964
+ }
7965
+ database.gcOrphanCallEdges();
7966
+ database.gcOrphanSymbols();
7967
+ database.gcOrphanEmbeddings();
7968
+ database.gcOrphanChunks();
7969
+ store.save();
7970
+ invertedIndex.save();
7971
+ return {
7972
+ removedChunkIds: removedChunkIdList,
7973
+ hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
7974
+ };
7975
+ }
7976
+ checkForInterruptedIndexing() {
7977
+ return existsSync6(this.indexingLockPath);
7978
+ }
7979
+ acquireIndexingLock() {
7980
+ const lockData = {
7981
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7982
+ pid: process.pid
7983
+ };
7984
+ writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
7985
+ }
7986
+ releaseIndexingLock() {
7987
+ if (existsSync6(this.indexingLockPath)) {
7988
+ unlinkSync(this.indexingLockPath);
7989
+ }
7990
+ }
7991
+ async recoverFromInterruptedIndexing() {
7992
+ this.logger.warn("Detected interrupted indexing session, recovering...");
7993
+ if (existsSync6(this.fileHashCachePath)) {
7994
+ unlinkSync(this.fileHashCachePath);
7995
+ }
7996
+ await this.healthCheck();
7997
+ this.releaseIndexingLock();
7998
+ this.logger.info("Recovery complete, next index will re-process all files");
7999
+ }
8000
+ loadFailedBatches(maxChunkTokens) {
8001
+ try {
8002
+ return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8003
+ } catch {
8004
+ return [];
8005
+ }
8006
+ }
8007
+ loadSerializedFailedBatches() {
8008
+ if (!existsSync6(this.failedBatchesPath)) {
8009
+ return [];
8010
+ }
8011
+ const data = readFileSync5(this.failedBatchesPath, "utf-8");
8012
+ const parsed = JSON.parse(data);
8013
+ return parsed.map((batch) => {
8014
+ const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
8015
+ if (chunks.length === 0) {
8016
+ return null;
8017
+ }
8018
+ return {
8019
+ chunks,
8020
+ error: typeof batch.error === "string" ? batch.error : "Unknown embedding error",
8021
+ attemptCount: typeof batch.attemptCount === "number" ? batch.attemptCount : 1,
8022
+ lastAttempt: typeof batch.lastAttempt === "string" ? batch.lastAttempt : (/* @__PURE__ */ new Date()).toISOString()
8023
+ };
8024
+ }).filter((batch) => batch !== null);
8025
+ }
8026
+ saveFailedBatches(batches) {
8027
+ if (batches.length === 0) {
8028
+ if (existsSync6(this.failedBatchesPath)) {
8029
+ try {
8030
+ unlinkSync(this.failedBatchesPath);
8031
+ } catch {
8032
+ }
8033
+ }
8034
+ return;
8035
+ }
8036
+ writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8037
+ }
8038
+ collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8039
+ const retryableById = /* @__PURE__ */ new Map();
8040
+ for (const batch of this.loadFailedBatches(maxChunkTokens)) {
8041
+ for (const chunk of batch.chunks) {
8042
+ const filePath = chunk.metadata.filePath;
8043
+ if (!currentFileHashes.has(filePath)) {
8044
+ continue;
8045
+ }
8046
+ if (!unchangedFilePaths.has(filePath)) {
8047
+ continue;
8048
+ }
8049
+ const existing = retryableById.get(chunk.id);
8050
+ if (!existing || batch.attemptCount > existing.attemptCount) {
8051
+ retryableById.set(chunk.id, {
8052
+ chunk,
8053
+ attemptCount: batch.attemptCount
8054
+ });
8055
+ }
8056
+ }
8057
+ }
8058
+ return Array.from(retryableById.values());
8059
+ }
8060
+ getProviderRateLimits(provider) {
8061
+ switch (provider) {
8062
+ case "github-copilot":
8063
+ return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
8064
+ case "openai":
8065
+ return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
8066
+ case "google":
8067
+ return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
8068
+ case "ollama":
8069
+ return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
8070
+ case "custom": {
8071
+ const customConfig = this.config.customProvider;
8072
+ return {
8073
+ concurrency: customConfig?.concurrency ?? 3,
8074
+ intervalMs: customConfig?.requestIntervalMs ?? 1e3,
8075
+ minRetryMs: 1e3,
8076
+ maxRetryMs: 3e4
8077
+ };
8078
+ }
8079
+ default:
8080
+ return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
8081
+ }
8082
+ }
8083
+ async rerankCandidatesWithApi(query, candidates, options) {
8084
+ const reranker = this.config.reranker;
8085
+ if (!reranker || !reranker.enabled || candidates.length <= 1) {
8086
+ return candidates;
8087
+ }
8088
+ const queryTokens = Array.from(tokenizeTextForRanking(query));
8089
+ const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
8090
+ const docIntent = classifyDocIntent(queryTokens) === "docs";
7153
8091
  if (options?.definitionIntent === true) {
7154
8092
  return candidates;
7155
8093
  }
@@ -7322,31 +8260,54 @@ var Indexer = class {
7322
8260
  }
7323
8261
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
7324
8262
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7325
- const storePath = path7.join(this.indexPath, "vectors");
8263
+ const storePath = path8.join(this.indexPath, "vectors");
7326
8264
  this.store = new VectorStore(storePath, dimensions);
7327
- const indexFilePath = path7.join(this.indexPath, "vectors.usearch");
7328
- if (existsSync5(indexFilePath)) {
8265
+ const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8266
+ if (existsSync6(indexFilePath)) {
7329
8267
  this.store.load();
7330
8268
  }
7331
- const invertedIndexPath = path7.join(this.indexPath, "inverted-index.json");
8269
+ const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
7332
8270
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7333
8271
  try {
7334
8272
  this.invertedIndex.load();
7335
8273
  } catch {
7336
- if (existsSync5(invertedIndexPath)) {
8274
+ if (existsSync6(invertedIndexPath)) {
7337
8275
  await fsPromises2.unlink(invertedIndexPath);
7338
8276
  }
7339
8277
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7340
8278
  }
7341
- const dbPath = path7.join(this.indexPath, "codebase.db");
7342
- const dbIsNew = !existsSync5(dbPath);
7343
- this.database = new Database(dbPath);
8279
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8280
+ let dbIsNew = !existsSync6(dbPath);
8281
+ try {
8282
+ this.database = new Database(dbPath);
8283
+ } catch (error) {
8284
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
8285
+ throw error;
8286
+ }
8287
+ this.store = new VectorStore(storePath, dimensions);
8288
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
8289
+ this.database = new Database(dbPath);
8290
+ dbIsNew = true;
8291
+ }
8292
+ if (isGitRepo(this.projectRoot)) {
8293
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
8294
+ this.baseBranch = getBaseBranch(this.projectRoot);
8295
+ this.logger.branch("info", "Detected git repository", {
8296
+ currentBranch: this.currentBranch,
8297
+ baseBranch: this.baseBranch
8298
+ });
8299
+ } else {
8300
+ this.currentBranch = "default";
8301
+ this.baseBranch = "default";
8302
+ this.logger.branch("debug", "Not a git repository, using default branch");
8303
+ }
7344
8304
  if (this.checkForInterruptedIndexing()) {
7345
8305
  await this.recoverFromInterruptedIndexing();
7346
8306
  }
7347
8307
  if (dbIsNew && this.store.count() > 0) {
7348
8308
  this.migrateFromLegacyIndex();
7349
8309
  }
8310
+ this.loadFileHashCache();
7350
8311
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7351
8312
  if (!this.indexCompatibility.compatible) {
7352
8313
  this.logger.warn("Index compatibility issue detected", {
@@ -7355,18 +8316,6 @@ var Indexer = class {
7355
8316
  configuredProviderInfo: this.configuredProviderInfo
7356
8317
  });
7357
8318
  }
7358
- if (isGitRepo(this.projectRoot)) {
7359
- this.currentBranch = getBranchOrDefault(this.projectRoot);
7360
- this.baseBranch = getBaseBranch(this.projectRoot);
7361
- this.logger.branch("info", "Detected git repository", {
7362
- currentBranch: this.currentBranch,
7363
- baseBranch: this.baseBranch
7364
- });
7365
- } else {
7366
- this.currentBranch = "default";
7367
- this.baseBranch = "default";
7368
- this.logger.branch("debug", "Not a git repository, using default branch");
7369
- }
7370
8319
  if (this.config.indexing.autoGc) {
7371
8320
  await this.maybeRunAutoGc();
7372
8321
  }
@@ -7386,20 +8335,169 @@ var Indexer = class {
7386
8335
  }
7387
8336
  }
7388
8337
  if (shouldRunGc) {
7389
- await this.healthCheck();
8338
+ const result = await this.healthCheck();
8339
+ if (result.warning) {
8340
+ this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
8341
+ } else {
8342
+ this.database.deleteMetadata(STARTUP_WARNING_METADATA_KEY);
8343
+ }
7390
8344
  this.database.setMetadata("lastGcTimestamp", now.toString());
7391
8345
  }
7392
8346
  }
7393
8347
  async maybeRunOrphanGc() {
7394
- if (!this.database) return;
8348
+ if (!this.database) return null;
7395
8349
  const stats = this.database.getStats();
7396
- if (!stats) return;
8350
+ if (!stats) return null;
7397
8351
  const orphanCount = stats.embeddingCount - stats.chunkCount;
7398
8352
  if (orphanCount > this.config.indexing.gcOrphanThreshold) {
7399
- this.database.gcOrphanEmbeddings();
7400
- this.database.gcOrphanChunks();
8353
+ try {
8354
+ this.database.gcOrphanEmbeddings();
8355
+ this.database.gcOrphanChunks();
8356
+ } catch (error) {
8357
+ if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8358
+ return {
8359
+ resetCorruptedIndex: true,
8360
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8361
+ };
8362
+ }
8363
+ throw error;
8364
+ }
7401
8365
  this.database.setMetadata("lastGcTimestamp", Date.now().toString());
7402
8366
  }
8367
+ return null;
8368
+ }
8369
+ rebuildVectorStoreExcludingChunkIds(store, database, excludedChunkIds) {
8370
+ const excludedSet = new Set(excludedChunkIds);
8371
+ if (excludedSet.size === 0) {
8372
+ return;
8373
+ }
8374
+ const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8375
+ const storeBasePath = path8.join(this.indexPath, "vectors");
8376
+ const storeIndexPath = `${storeBasePath}.usearch`;
8377
+ const storeMetadataPath = `${storeBasePath}.meta.json`;
8378
+ const backupIndexPath = `${storeIndexPath}.bak`;
8379
+ const backupMetadataPath = `${storeMetadataPath}.bak`;
8380
+ let backedUpIndex = false;
8381
+ let backedUpMetadata = false;
8382
+ let rebuiltCount = 0;
8383
+ let skippedCount = 0;
8384
+ if (existsSync6(backupIndexPath)) {
8385
+ unlinkSync(backupIndexPath);
8386
+ }
8387
+ if (existsSync6(backupMetadataPath)) {
8388
+ unlinkSync(backupMetadataPath);
8389
+ }
8390
+ try {
8391
+ if (existsSync6(storeIndexPath)) {
8392
+ renameSync(storeIndexPath, backupIndexPath);
8393
+ backedUpIndex = true;
8394
+ }
8395
+ if (existsSync6(storeMetadataPath)) {
8396
+ renameSync(storeMetadataPath, backupMetadataPath);
8397
+ backedUpMetadata = true;
8398
+ }
8399
+ store.clear();
8400
+ for (const { key, metadata } of retainedEntries) {
8401
+ const chunk = database.getChunk(key);
8402
+ if (!chunk) {
8403
+ skippedCount += 1;
8404
+ continue;
8405
+ }
8406
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8407
+ if (!embeddingBuffer) {
8408
+ skippedCount += 1;
8409
+ continue;
8410
+ }
8411
+ const vector = bufferToFloat32Array(embeddingBuffer);
8412
+ store.add(key, Array.from(vector), metadata);
8413
+ rebuiltCount += 1;
8414
+ }
8415
+ store.save();
8416
+ if (backedUpIndex && existsSync6(backupIndexPath)) {
8417
+ unlinkSync(backupIndexPath);
8418
+ }
8419
+ if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8420
+ unlinkSync(backupMetadataPath);
8421
+ }
8422
+ this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8423
+ excludedChunks: excludedSet.size,
8424
+ rebuiltChunks: rebuiltCount,
8425
+ skippedChunks: skippedCount
8426
+ });
8427
+ } catch (error) {
8428
+ try {
8429
+ store.clear();
8430
+ } catch {
8431
+ }
8432
+ if (existsSync6(storeIndexPath)) {
8433
+ unlinkSync(storeIndexPath);
8434
+ }
8435
+ if (existsSync6(storeMetadataPath)) {
8436
+ unlinkSync(storeMetadataPath);
8437
+ }
8438
+ if (backedUpIndex && existsSync6(backupIndexPath)) {
8439
+ renameSync(backupIndexPath, storeIndexPath);
8440
+ }
8441
+ if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8442
+ renameSync(backupMetadataPath, storeMetadataPath);
8443
+ }
8444
+ if (backedUpIndex || backedUpMetadata) {
8445
+ store.load();
8446
+ }
8447
+ throw error;
8448
+ }
8449
+ }
8450
+ getCorruptedIndexWarning(dbPath) {
8451
+ if (this.config.scope === "global") {
8452
+ 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.`;
8453
+ }
8454
+ return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
8455
+ }
8456
+ async tryResetCorruptedIndex(stage, error) {
8457
+ if (!isSqliteCorruptionError(error)) {
8458
+ return false;
8459
+ }
8460
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8461
+ const warning = this.getCorruptedIndexWarning(dbPath);
8462
+ const errorMessage = getErrorMessage(error);
8463
+ if (this.config.scope === "global") {
8464
+ this.logger.error("Detected corrupted shared global index database", {
8465
+ stage,
8466
+ dbPath,
8467
+ error: errorMessage
8468
+ });
8469
+ throw new Error(`${warning} Original SQLite error: ${errorMessage}`);
8470
+ }
8471
+ this.logger.warn("Detected corrupted local index database, resetting local index", {
8472
+ stage,
8473
+ dbPath,
8474
+ error: errorMessage
8475
+ });
8476
+ this.store = null;
8477
+ this.invertedIndex = null;
8478
+ this.database?.close();
8479
+ this.database = null;
8480
+ this.indexCompatibility = null;
8481
+ this.fileHashCache.clear();
8482
+ const resetPaths = [
8483
+ path8.join(this.indexPath, "codebase.db"),
8484
+ path8.join(this.indexPath, "codebase.db-shm"),
8485
+ path8.join(this.indexPath, "codebase.db-wal"),
8486
+ path8.join(this.indexPath, "vectors.usearch"),
8487
+ path8.join(this.indexPath, "inverted-index.json"),
8488
+ path8.join(this.indexPath, "file-hashes.json"),
8489
+ path8.join(this.indexPath, "failed-batches.json"),
8490
+ path8.join(this.indexPath, "indexing.lock"),
8491
+ path8.join(this.indexPath, "vectors")
8492
+ ];
8493
+ await Promise.all(resetPaths.map(async (targetPath) => {
8494
+ try {
8495
+ await fsPromises2.rm(targetPath, { recursive: true, force: true });
8496
+ } catch {
8497
+ }
8498
+ }));
8499
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
8500
+ return true;
7403
8501
  }
7404
8502
  migrateFromLegacyIndex() {
7405
8503
  if (!this.store || !this.database) return;
@@ -7423,7 +8521,7 @@ var Indexer = class {
7423
8521
  if (chunkDataBatch.length > 0) {
7424
8522
  this.database.upsertChunksBatch(chunkDataBatch);
7425
8523
  }
7426
- this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
8524
+ this.database.addChunksToBranchBatch(this.getBranchCatalogKey(), chunkIds);
7427
8525
  }
7428
8526
  loadIndexMetadata() {
7429
8527
  if (!this.database) return null;
@@ -7434,6 +8532,7 @@ var Indexer = class {
7434
8532
  embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
7435
8533
  embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
7436
8534
  embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
8535
+ embeddingStrategyVersion: this.loadStoredEmbeddingStrategyVersion() ?? EMBEDDING_STRATEGY_VERSION,
7437
8536
  createdAt: this.database.getMetadata("index.createdAt") ?? "",
7438
8537
  updatedAt: this.database.getMetadata("index.updatedAt") ?? ""
7439
8538
  };
@@ -7442,10 +8541,22 @@ var Indexer = class {
7442
8541
  if (!this.database) return;
7443
8542
  const now = (/* @__PURE__ */ new Date()).toISOString();
7444
8543
  const existingCreatedAt = this.database.getMetadata("index.createdAt");
8544
+ const completeProjectEmbeddingStrategyReset = !this.hasProjectForceReembedPending();
7445
8545
  this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
7446
8546
  this.database.setMetadata("index.embeddingProvider", provider.provider);
7447
8547
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
7448
8548
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8549
+ if (this.config.scope === "global") {
8550
+ if (completeProjectEmbeddingStrategyReset) {
8551
+ this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
8552
+ }
8553
+ this.database.setMetadata(this.getLegacyMigrationMetadataKey(), "done");
8554
+ if (completeProjectEmbeddingStrategyReset) {
8555
+ this.database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8556
+ }
8557
+ } else {
8558
+ this.database.setMetadata("index.embeddingStrategyVersion", EMBEDDING_STRATEGY_VERSION);
8559
+ }
7449
8560
  this.database.setMetadata("index.updatedAt", now);
7450
8561
  if (!existingCreatedAt) {
7451
8562
  this.database.setMetadata("index.createdAt", now);
@@ -7475,6 +8586,14 @@ var Indexer = class {
7475
8586
  storedMetadata
7476
8587
  };
7477
8588
  }
8589
+ if (storedMetadata.embeddingStrategyVersion !== EMBEDDING_STRATEGY_VERSION) {
8590
+ return {
8591
+ compatible: false,
8592
+ code: "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */,
8593
+ 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.`,
8594
+ storedMetadata
8595
+ };
8596
+ }
7478
8597
  if (storedMetadata.embeddingProvider !== currentProvider) {
7479
8598
  this.logger.warn("Provider changed", {
7480
8599
  storedProvider: storedMetadata.embeddingProvider,
@@ -7522,6 +8641,10 @@ var Indexer = class {
7522
8641
  }
7523
8642
  async index(onProgress) {
7524
8643
  const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8644
+ const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
8645
+ const branchCatalogKey = this.getBranchCatalogKey();
8646
+ const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
8647
+ const failedForcedChunkIds = /* @__PURE__ */ new Set();
7525
8648
  if (!this.indexCompatibility?.compatible) {
7526
8649
  throw new Error(
7527
8650
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
@@ -7543,6 +8666,7 @@ var Indexer = class {
7543
8666
  skippedFiles: [],
7544
8667
  parseFailures: []
7545
8668
  };
8669
+ const failedBatchesForCurrentRun = [];
7546
8670
  onProgress?.({
7547
8671
  phase: "scanning",
7548
8672
  filesProcessed: 0,
@@ -7602,6 +8726,12 @@ var Indexer = class {
7602
8726
  const existingChunks = /* @__PURE__ */ new Map();
7603
8727
  const existingChunksByFile = /* @__PURE__ */ new Map();
7604
8728
  for (const { key, metadata } of store.getAllMetadata()) {
8729
+ if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8730
+ continue;
8731
+ }
8732
+ if (forceScopedReembed && scopedRoots && this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8733
+ continue;
8734
+ }
7605
8735
  existingChunks.set(key, metadata.hash);
7606
8736
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7607
8737
  fileChunks.add(key);
@@ -7623,7 +8753,7 @@ var Indexer = class {
7623
8753
  for (const parsed of parsedFiles) {
7624
8754
  currentFilePaths.add(parsed.path);
7625
8755
  if (parsed.chunks.length === 0) {
7626
- const relativePath = path7.relative(this.projectRoot, parsed.path);
8756
+ const relativePath = path8.relative(this.projectRoot, parsed.path);
7627
8757
  stats.parseFailures.push(relativePath);
7628
8758
  }
7629
8759
  let fileChunkCount = 0;
@@ -7659,7 +8789,10 @@ var Indexer = class {
7659
8789
  fileChunkCount++;
7660
8790
  continue;
7661
8791
  }
7662
- const text = createEmbeddingText(chunk, parsed.path);
8792
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text) => ({
8793
+ text,
8794
+ tokenCount: estimateTokens(text)
8795
+ }));
7663
8796
  const metadata = {
7664
8797
  filePath: parsed.path,
7665
8798
  startLine: chunk.startLine,
@@ -7669,10 +8802,38 @@ var Indexer = class {
7669
8802
  language: chunk.language,
7670
8803
  hash: contentHash
7671
8804
  };
7672
- pendingChunks.push({ id, text, content: chunk.content, contentHash, metadata });
8805
+ pendingChunks.push({
8806
+ id,
8807
+ texts,
8808
+ storageText: createPendingChunkStorageText(texts),
8809
+ content: chunk.content,
8810
+ contentHash,
8811
+ metadata
8812
+ });
7673
8813
  fileChunkCount++;
7674
8814
  }
7675
8815
  }
8816
+ const retryableFailedChunks = this.collectRetryableFailedChunks(
8817
+ currentFileHashes,
8818
+ unchangedFilePaths,
8819
+ getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)
8820
+ );
8821
+ const retryableFailedAttemptCounts = /* @__PURE__ */ new Map();
8822
+ const retryableChunksWithExistingData = /* @__PURE__ */ new Set();
8823
+ if (retryableFailedChunks.length > 0) {
8824
+ const pendingChunkIds = new Set(pendingChunks.map((chunk) => chunk.id));
8825
+ for (const { chunk, attemptCount } of retryableFailedChunks) {
8826
+ retryableFailedAttemptCounts.set(chunk.id, attemptCount);
8827
+ if (existingChunks.has(chunk.id)) {
8828
+ retryableChunksWithExistingData.add(chunk.id);
8829
+ }
8830
+ if (!pendingChunkIds.has(chunk.id)) {
8831
+ pendingChunks.push(chunk);
8832
+ pendingChunkIds.add(chunk.id);
8833
+ currentChunkIds.add(chunk.id);
8834
+ }
8835
+ }
8836
+ }
7676
8837
  if (chunkDataBatch.length > 0) {
7677
8838
  database.upsertChunksBatch(chunkDataBatch);
7678
8839
  }
@@ -7701,17 +8862,20 @@ var Indexer = class {
7701
8862
  fileSymbols.push(symbol);
7702
8863
  allSymbolIds.add(symbolId);
7703
8864
  }
8865
+ const fileLanguage = parsed.chunks[0]?.language;
8866
+ const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
8867
+ const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
7704
8868
  const symbolsByName = /* @__PURE__ */ new Map();
7705
8869
  for (const symbol of fileSymbols) {
7706
- const existing = symbolsByName.get(symbol.name) ?? [];
8870
+ const key = normalizeSymbolKey(symbol.name);
8871
+ const existing = symbolsByName.get(key) ?? [];
7707
8872
  existing.push(symbol);
7708
- symbolsByName.set(symbol.name, existing);
8873
+ symbolsByName.set(key, existing);
7709
8874
  }
7710
8875
  if (fileSymbols.length > 0) {
7711
8876
  database.upsertSymbolsBatch(fileSymbols);
7712
8877
  symbolsByFile.set(parsed.path, fileSymbols);
7713
8878
  }
7714
- const fileLanguage = parsed.chunks[0]?.language;
7715
8879
  if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
7716
8880
  const callSites = extractCalls(changedFile.content, fileLanguage);
7717
8881
  if (callSites.length === 0) continue;
@@ -7736,7 +8900,7 @@ var Indexer = class {
7736
8900
  if (edges.length > 0) {
7737
8901
  database.upsertCallEdgesBatch(edges);
7738
8902
  for (const edge of edges) {
7739
- const candidates = symbolsByName.get(edge.targetName);
8903
+ const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
7740
8904
  if (candidates && candidates.length === 1) {
7741
8905
  database.resolveCallEdge(edge.id, candidates[0].id);
7742
8906
  }
@@ -7749,14 +8913,20 @@ var Indexer = class {
7749
8913
  allSymbolIds.add(sym.id);
7750
8914
  }
7751
8915
  }
7752
- let removedCount = 0;
8916
+ const removedChunkIds = [];
7753
8917
  for (const [chunkId] of existingChunks) {
7754
8918
  if (!currentChunkIds.has(chunkId)) {
7755
- store.remove(chunkId);
8919
+ removedChunkIds.push(chunkId);
8920
+ }
8921
+ }
8922
+ if (removedChunkIds.length > 0) {
8923
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkIds);
8924
+ for (const chunkId of removedChunkIds) {
7756
8925
  invertedIndex.removeChunk(chunkId);
7757
- removedCount++;
7758
8926
  }
8927
+ database.deleteChunksByIds(removedChunkIds);
7759
8928
  }
8929
+ const removedCount = removedChunkIds.length;
7760
8930
  stats.totalChunks = pendingChunks.length;
7761
8931
  stats.existingChunks = currentChunkIds.size - pendingChunks.length;
7762
8932
  stats.removedChunks = removedCount;
@@ -7768,12 +8938,20 @@ var Indexer = class {
7768
8938
  removed: removedCount
7769
8939
  });
7770
8940
  if (pendingChunks.length === 0 && removedCount === 0) {
7771
- database.clearBranch(this.currentBranch);
7772
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7773
- database.clearBranchSymbols(this.currentBranch);
7774
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
7775
- this.fileHashCache = currentFileHashes;
7776
- this.saveFileHashCache();
8941
+ database.clearBranch(branchCatalogKey);
8942
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8943
+ database.clearBranchSymbols(branchCatalogKey);
8944
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8945
+ if (scopedRoots) {
8946
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8947
+ this.clearScopedFailedBatches(scopedRoots);
8948
+ } else {
8949
+ this.fileHashCache = currentFileHashes;
8950
+ this.saveFileHashCache();
8951
+ this.saveFailedBatches([]);
8952
+ }
8953
+ this.saveIndexMetadata(configuredProviderInfo);
8954
+ this.indexCompatibility = { compatible: true };
7777
8955
  stats.durationMs = Date.now() - startTime;
7778
8956
  onProgress?.({
7779
8957
  phase: "complete",
@@ -7786,14 +8964,22 @@ var Indexer = class {
7786
8964
  return stats;
7787
8965
  }
7788
8966
  if (pendingChunks.length === 0) {
7789
- database.clearBranch(this.currentBranch);
7790
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7791
- database.clearBranchSymbols(this.currentBranch);
7792
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
8967
+ database.clearBranch(branchCatalogKey);
8968
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8969
+ database.clearBranchSymbols(branchCatalogKey);
8970
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7793
8971
  store.save();
7794
8972
  invertedIndex.save();
7795
- this.fileHashCache = currentFileHashes;
7796
- this.saveFileHashCache();
8973
+ if (scopedRoots) {
8974
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8975
+ this.clearScopedFailedBatches(scopedRoots);
8976
+ } else {
8977
+ this.fileHashCache = currentFileHashes;
8978
+ this.saveFileHashCache();
8979
+ this.saveFailedBatches([]);
8980
+ }
8981
+ this.saveIndexMetadata(configuredProviderInfo);
8982
+ this.indexCompatibility = { compatible: true };
7797
8983
  stats.durationMs = Date.now() - startTime;
7798
8984
  onProgress?.({
7799
8985
  phase: "complete",
@@ -7814,8 +9000,9 @@ var Indexer = class {
7814
9000
  });
7815
9001
  const allContentHashes = pendingChunks.map((c) => c.contentHash);
7816
9002
  const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
7817
- const chunksNeedingEmbedding = pendingChunks.filter((c) => missingHashes.has(c.contentHash));
7818
- const chunksWithExistingEmbedding = pendingChunks.filter((c) => !missingHashes.has(c.contentHash));
9003
+ const forcedReembedChunkIds = forceScopedReembed ? new Set(pendingChunks.map((chunk) => chunk.id)) : /* @__PURE__ */ new Set();
9004
+ const chunksNeedingEmbedding = pendingChunks.filter((c) => forcedReembedChunkIds.has(c.id) || missingHashes.has(c.contentHash));
9005
+ const chunksWithExistingEmbedding = pendingChunks.filter((c) => !forcedReembedChunkIds.has(c.id) && !missingHashes.has(c.contentHash));
7819
9006
  this.logger.cache("info", "Embedding cache lookup", {
7820
9007
  needsEmbedding: chunksNeedingEmbedding.length,
7821
9008
  fromCache: chunksWithExistingEmbedding.length
@@ -7837,17 +9024,24 @@ var Indexer = class {
7837
9024
  interval: providerRateLimits.intervalMs,
7838
9025
  intervalCap: providerRateLimits.concurrency
7839
9026
  });
7840
- const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
9027
+ const pendingChunksById = new Map(chunksNeedingEmbedding.map((chunk) => [chunk.id, chunk]));
9028
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9029
+ const completedChunkIds = /* @__PURE__ */ new Set();
9030
+ const failedChunkIds = /* @__PURE__ */ new Set();
9031
+ const requestBatches = createPendingEmbeddingRequestBatches(
9032
+ chunksNeedingEmbedding,
9033
+ getDynamicBatchOptions(configuredProviderInfo)
9034
+ );
7841
9035
  let rateLimitBackoffMs = 0;
7842
- for (const batch of dynamicBatches) {
9036
+ for (const requestBatch of requestBatches) {
7843
9037
  queue.add(async () => {
7844
9038
  if (rateLimitBackoffMs > 0) {
7845
- await new Promise((resolve7) => setTimeout(resolve7, rateLimitBackoffMs));
9039
+ await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
7846
9040
  }
7847
9041
  try {
7848
9042
  const result = await pRetry(
7849
9043
  async () => {
7850
- const texts = batch.map((c) => c.text);
9044
+ const texts = requestBatch.map((request) => request.text);
7851
9045
  return provider.embedBatch(texts);
7852
9046
  },
7853
9047
  {
@@ -7877,29 +9071,82 @@ var Indexer = class {
7877
9071
  if (rateLimitBackoffMs > 0) {
7878
9072
  rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2e3);
7879
9073
  }
7880
- const items = batch.map((chunk, idx) => ({
7881
- id: chunk.id,
7882
- vector: result.embeddings[idx],
7883
- metadata: chunk.metadata
7884
- }));
7885
- store.addBatch(items);
7886
- const embeddingBatchItems = batch.map((chunk, i) => ({
7887
- contentHash: chunk.contentHash,
7888
- embedding: float32ArrayToBuffer(result.embeddings[i]),
7889
- chunkText: chunk.text,
7890
- model: configuredProviderInfo.modelInfo.model
7891
- }));
7892
- database.upsertEmbeddingsBatch(embeddingBatchItems);
7893
- for (const chunk of batch) {
7894
- invertedIndex.removeChunk(chunk.id);
7895
- invertedIndex.addChunk(chunk.id, chunk.content);
9074
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9075
+ requestBatch.forEach((request, idx) => {
9076
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9077
+ return;
9078
+ }
9079
+ const vector = result.embeddings[idx];
9080
+ if (!vector) {
9081
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9082
+ }
9083
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9084
+ parts[request.partIndex] = {
9085
+ vector,
9086
+ tokenCount: request.tokenCount
9087
+ };
9088
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9089
+ touchedChunkIds.add(request.chunk.id);
9090
+ });
9091
+ const pooledResults = [];
9092
+ for (const chunkId of touchedChunkIds) {
9093
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9094
+ continue;
9095
+ }
9096
+ const chunk = pendingChunksById.get(chunkId);
9097
+ if (!chunk) {
9098
+ continue;
9099
+ }
9100
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9101
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9102
+ continue;
9103
+ }
9104
+ const orderedParts = parts;
9105
+ pooledResults.push({
9106
+ chunk,
9107
+ vector: poolEmbeddingVectors(
9108
+ orderedParts.map((part) => part.vector),
9109
+ orderedParts.map((part) => part.tokenCount)
9110
+ )
9111
+ });
9112
+ }
9113
+ if (pooledResults.length > 0) {
9114
+ const items = pooledResults.map(({ chunk, vector }) => ({
9115
+ id: chunk.id,
9116
+ vector,
9117
+ metadata: chunk.metadata
9118
+ }));
9119
+ store.addBatch(items);
9120
+ const embeddingBatchItems = pooledResults.map(({ chunk, vector }) => ({
9121
+ contentHash: chunk.contentHash,
9122
+ embedding: float32ArrayToBuffer(vector),
9123
+ chunkText: chunk.storageText,
9124
+ model: configuredProviderInfo.modelInfo.model
9125
+ }));
9126
+ try {
9127
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
9128
+ } catch (dbError) {
9129
+ this.rebuildVectorStoreExcludingChunkIds(
9130
+ store,
9131
+ database,
9132
+ pooledResults.map(({ chunk }) => chunk.id)
9133
+ );
9134
+ throw dbError;
9135
+ }
9136
+ for (const { chunk } of pooledResults) {
9137
+ invertedIndex.removeChunk(chunk.id);
9138
+ invertedIndex.addChunk(chunk.id, chunk.content);
9139
+ completedChunkIds.add(chunk.id);
9140
+ embeddingPartsByChunk.delete(chunk.id);
9141
+ }
9142
+ stats.indexedChunks += pooledResults.length;
9143
+ this.logger.recordChunksEmbedded(pooledResults.length);
7896
9144
  }
7897
- stats.indexedChunks += batch.length;
7898
9145
  stats.tokensUsed += result.totalTokensUsed;
7899
- this.logger.recordChunksEmbedded(batch.length);
7900
9146
  this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
7901
9147
  this.logger.embedding("debug", `Embedded batch`, {
7902
- batchSize: batch.length,
9148
+ batchSize: pooledResults.length,
9149
+ requestCount: requestBatch.length,
7903
9150
  tokens: result.totalTokensUsed
7904
9151
  });
7905
9152
  onProgress?.({
@@ -7910,17 +9157,49 @@ var Indexer = class {
7910
9157
  totalChunks: pendingChunks.length
7911
9158
  });
7912
9159
  } catch (error) {
7913
- stats.failedChunks += batch.length;
7914
- this.addFailedBatch(batch, getErrorMessage(error));
9160
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
9161
+ const failureMessage = getErrorMessage(error);
9162
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9163
+ for (const chunk of failedChunks) {
9164
+ if (!failedChunkIds.has(chunk.id)) {
9165
+ failedChunkIds.add(chunk.id);
9166
+ stats.failedChunks += 1;
9167
+ }
9168
+ if (forceScopedReembed) {
9169
+ failedForcedChunkIds.add(chunk.id);
9170
+ }
9171
+ embeddingPartsByChunk.delete(chunk.id);
9172
+ const existingFailedBatchIndex = failedBatchesForCurrentRun.findIndex(
9173
+ (failedBatch2) => failedBatch2.chunks[0]?.id === chunk.id
9174
+ );
9175
+ const existingFailedBatch = existingFailedBatchIndex === -1 ? void 0 : failedBatchesForCurrentRun[existingFailedBatchIndex];
9176
+ const failedBatch = {
9177
+ chunks: [chunk],
9178
+ error: failureMessage,
9179
+ attemptCount: (existingFailedBatch?.attemptCount ?? retryableFailedAttemptCounts.get(chunk.id) ?? 0) + 1,
9180
+ lastAttempt: failureTimestamp
9181
+ };
9182
+ if (existingFailedBatchIndex === -1) {
9183
+ failedBatchesForCurrentRun.push(failedBatch);
9184
+ } else {
9185
+ failedBatchesForCurrentRun[existingFailedBatchIndex] = failedBatch;
9186
+ }
9187
+ }
7915
9188
  this.logger.recordEmbeddingError();
7916
9189
  this.logger.embedding("error", `Failed to embed batch after retries`, {
7917
- batchSize: batch.length,
7918
- error: getErrorMessage(error)
9190
+ batchSize: failedChunks.length,
9191
+ requestCount: requestBatch.length,
9192
+ error: failureMessage
7919
9193
  });
7920
9194
  }
7921
9195
  });
7922
9196
  }
7923
9197
  await queue.onIdle();
9198
+ if (scopedRoots) {
9199
+ this.saveScopedFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun), scopedRoots);
9200
+ } else {
9201
+ this.saveFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun));
9202
+ }
7924
9203
  onProgress?.({
7925
9204
  phase: "storing",
7926
9205
  filesProcessed: files.length,
@@ -7928,18 +9207,48 @@ var Indexer = class {
7928
9207
  chunksProcessed: stats.indexedChunks,
7929
9208
  totalChunks: pendingChunks.length
7930
9209
  });
7931
- database.clearBranch(this.currentBranch);
7932
- database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
7933
- database.clearBranchSymbols(this.currentBranch);
7934
- database.addSymbolsToBranchBatch(this.currentBranch, Array.from(allSymbolIds));
9210
+ const branchChunkIds = Array.from(currentChunkIds).filter(
9211
+ (chunkId) => {
9212
+ const isNewlyFailed = failedChunkIds.has(chunkId) && !retryableChunksWithExistingData.has(chunkId);
9213
+ const isForcedFailed = forceScopedReembed && failedForcedChunkIds.has(chunkId);
9214
+ return !isNewlyFailed && !isForcedFailed;
9215
+ }
9216
+ );
9217
+ database.clearBranch(branchCatalogKey);
9218
+ database.addChunksToBranchBatch(branchCatalogKey, branchChunkIds);
9219
+ database.clearBranchSymbols(branchCatalogKey);
9220
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7935
9221
  store.save();
7936
9222
  invertedIndex.save();
7937
- this.fileHashCache = currentFileHashes;
7938
- this.saveFileHashCache();
9223
+ if (scopedRoots) {
9224
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
9225
+ } else {
9226
+ this.fileHashCache = currentFileHashes;
9227
+ this.saveFileHashCache();
9228
+ }
7939
9229
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
7940
- await this.maybeRunOrphanGc();
9230
+ const gcReset = await this.maybeRunOrphanGc();
9231
+ if (gcReset) {
9232
+ stats.durationMs = Date.now() - startTime;
9233
+ stats.warning = gcReset.warning;
9234
+ stats.resetCorruptedIndex = true;
9235
+ this.logger.recordIndexingEnd();
9236
+ this.logger.warn("Indexing ended after resetting corrupted local index during automatic GC", {
9237
+ files: stats.totalFiles,
9238
+ indexed: stats.indexedChunks,
9239
+ existing: stats.existingChunks,
9240
+ removed: stats.removedChunks,
9241
+ failed: stats.failedChunks,
9242
+ tokens: stats.tokensUsed,
9243
+ durationMs: stats.durationMs
9244
+ });
9245
+ return stats;
9246
+ }
7941
9247
  }
7942
9248
  stats.durationMs = Date.now() - startTime;
9249
+ if (forceScopedReembed && failedForcedChunkIds.size === 0) {
9250
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9251
+ }
7943
9252
  this.saveIndexMetadata(configuredProviderInfo);
7944
9253
  this.indexCompatibility = { compatible: true };
7945
9254
  this.logger.recordIndexingEnd();
@@ -8068,27 +9377,30 @@ var Indexer = class {
8068
9377
  const keywordResults = await this.keywordSearch(query, maxResults * 4);
8069
9378
  const keywordMs = performance2.now() - keywordStartTime;
8070
9379
  let branchChunkIds = null;
8071
- if (filterByBranch && this.currentBranch !== "default") {
8072
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9380
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9381
+ branchChunkIds = new Set(
9382
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9383
+ );
8073
9384
  }
8074
9385
  const prefilterStartTime = performance2.now();
8075
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9386
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9387
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8076
9388
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8077
9389
  const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
8078
- const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
8079
- const keywordCandidates = shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
9390
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9391
+ const keywordCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
8080
9392
  const prefilterMs = performance2.now() - prefilterStartTime;
8081
- if (branchChunkIds && branchChunkIds.size === 0) {
9393
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8082
9394
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8083
9395
  branch: this.currentBranch
8084
9396
  });
8085
9397
  }
8086
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9398
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8087
9399
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8088
9400
  branch: this.currentBranch
8089
9401
  });
8090
9402
  }
8091
- if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
9403
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
8092
9404
  this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
8093
9405
  branch: this.currentBranch
8094
9406
  });
@@ -8141,26 +9453,13 @@ var Indexer = class {
8141
9453
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
8142
9454
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
8143
9455
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
8144
- const baseFiltered = tiered.filter((r) => {
8145
- if (r.score < this.config.search.minScore) return false;
8146
- if (options?.fileType) {
8147
- const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
8148
- if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
8149
- }
8150
- if (options?.directory) {
8151
- const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
8152
- if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
8153
- }
8154
- if (options?.chunkType) {
8155
- if (r.metadata.chunkType !== options.chunkType) return false;
8156
- }
8157
- return true;
8158
- });
9456
+ const baseFiltered = tiered.filter((r) => matchesSearchFilters(r, options, this.config.search.minScore));
8159
9457
  const implementationOnly = baseFiltered.filter(
8160
9458
  (r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
8161
9459
  );
8162
9460
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
8163
- const finalResults = filtered;
9461
+ 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) : [];
9462
+ const finalResults = filtered.length > 0 ? filtered : identifierFallback;
8164
9463
  const totalSearchMs = performance2.now() - searchStartTime;
8165
9464
  this.logger.recordSearch(totalSearchMs, {
8166
9465
  embeddingMs,
@@ -8230,7 +9529,8 @@ var Indexer = class {
8230
9529
  return results.slice(0, limit);
8231
9530
  }
8232
9531
  async getStatus() {
8233
- const { store, configuredProviderInfo } = await this.ensureInitialized();
9532
+ const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9533
+ const failedBatchesCount = this.getFailedBatchesCount();
8234
9534
  return {
8235
9535
  indexed: store.count() > 0,
8236
9536
  vectorCount: store.count(),
@@ -8239,23 +9539,86 @@ var Indexer = class {
8239
9539
  indexPath: this.indexPath,
8240
9540
  currentBranch: this.currentBranch,
8241
9541
  baseBranch: this.baseBranch,
8242
- compatibility: this.indexCompatibility
9542
+ compatibility: this.indexCompatibility,
9543
+ failedBatchesCount,
9544
+ failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
9545
+ warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
8243
9546
  };
8244
9547
  }
8245
9548
  async clearIndex() {
8246
9549
  const { store, invertedIndex, database } = await this.ensureInitialized();
9550
+ if (this.config.scope === "global") {
9551
+ store.load();
9552
+ invertedIndex.load();
9553
+ this.loadFileHashCache();
9554
+ const roots = this.getScopedRoots();
9555
+ const compatibility = this.checkCompatibility();
9556
+ const allMetadata = store.getAllMetadata();
9557
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
9558
+ if (!compatibility.compatible && hasForeignData) {
9559
+ if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
9560
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9561
+ this.clearScopedFileHashCache(roots);
9562
+ this.clearScopedFailedBatches(roots);
9563
+ database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
9564
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9565
+ this.indexCompatibility = { compatible: true };
9566
+ return;
9567
+ }
9568
+ throw new Error(
9569
+ `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.`
9570
+ );
9571
+ }
9572
+ if (!hasForeignData) {
9573
+ store.clear();
9574
+ store.save();
9575
+ invertedIndex.clear();
9576
+ invertedIndex.save();
9577
+ this.fileHashCache.clear();
9578
+ this.saveFileHashCache();
9579
+ database.clearAllIndexedData();
9580
+ this.saveFailedBatches([]);
9581
+ database.deleteMetadata("index.version");
9582
+ database.deleteMetadata("index.embeddingProvider");
9583
+ database.deleteMetadata("index.embeddingModel");
9584
+ database.deleteMetadata("index.embeddingDimensions");
9585
+ database.deleteMetadata("index.embeddingStrategyVersion");
9586
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9587
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9588
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
9589
+ database.deleteMetadata("index.createdAt");
9590
+ database.deleteMetadata("index.updatedAt");
9591
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
9592
+ return;
9593
+ }
9594
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9595
+ this.clearScopedFileHashCache(roots);
9596
+ this.clearScopedFailedBatches(roots);
9597
+ this.indexCompatibility = compatibility;
9598
+ return;
9599
+ }
9600
+ const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9601
+ if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9602
+ throw new Error(
9603
+ "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9604
+ );
9605
+ }
8247
9606
  store.clear();
8248
9607
  store.save();
8249
9608
  invertedIndex.clear();
8250
9609
  invertedIndex.save();
8251
9610
  this.fileHashCache.clear();
8252
9611
  this.saveFileHashCache();
8253
- database.clearBranch(this.currentBranch);
8254
- database.clearBranchSymbols(this.currentBranch);
9612
+ database.clearAllIndexedData();
9613
+ this.saveFailedBatches([]);
8255
9614
  database.deleteMetadata("index.version");
8256
9615
  database.deleteMetadata("index.embeddingProvider");
8257
9616
  database.deleteMetadata("index.embeddingModel");
8258
9617
  database.deleteMetadata("index.embeddingDimensions");
9618
+ database.deleteMetadata("index.embeddingStrategyVersion");
9619
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9620
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9621
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
8259
9622
  database.deleteMetadata("index.createdAt");
8260
9623
  database.deleteMetadata("index.updatedAt");
8261
9624
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
@@ -8271,28 +9634,61 @@ var Indexer = class {
8271
9634
  filePathsToChunkKeys.set(metadata.filePath, existing);
8272
9635
  }
8273
9636
  const removedFilePaths = [];
8274
- let removedCount = 0;
9637
+ const removedChunkKeys = [];
9638
+ const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8275
9639
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8276
- if (!existsSync5(filePath)) {
9640
+ if (!existsSync6(filePath)) {
9641
+ chunkKeysByRemovedFile.set(filePath, chunkKeys);
8277
9642
  for (const key of chunkKeys) {
8278
- store.remove(key);
8279
- invertedIndex.removeChunk(key);
8280
- removedCount++;
9643
+ removedChunkKeys.push(key);
8281
9644
  }
8282
- database.deleteChunksByFile(filePath);
8283
- database.deleteCallEdgesByFile(filePath);
8284
- database.deleteSymbolsByFile(filePath);
8285
9645
  removedFilePaths.push(filePath);
8286
9646
  }
8287
9647
  }
9648
+ if (removedChunkKeys.length > 0) {
9649
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkKeys);
9650
+ for (const key of removedChunkKeys) {
9651
+ invertedIndex.removeChunk(key);
9652
+ }
9653
+ }
9654
+ for (const filePath of removedFilePaths) {
9655
+ const fileChunkKeys = chunkKeysByRemovedFile.get(filePath) ?? [];
9656
+ if (fileChunkKeys.length > 0) {
9657
+ database.deleteChunksByIds(fileChunkKeys);
9658
+ }
9659
+ database.deleteCallEdgesByFile(filePath);
9660
+ database.deleteSymbolsByFile(filePath);
9661
+ }
9662
+ const removedCount = removedChunkKeys.length;
8288
9663
  if (removedCount > 0) {
8289
9664
  store.save();
8290
9665
  invertedIndex.save();
8291
9666
  }
8292
- const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
8293
- const gcOrphanChunks = database.gcOrphanChunks();
8294
- const gcOrphanSymbols = database.gcOrphanSymbols();
8295
- const gcOrphanCallEdges = database.gcOrphanCallEdges();
9667
+ let gcOrphanEmbeddings;
9668
+ let gcOrphanChunks;
9669
+ let gcOrphanSymbols;
9670
+ let gcOrphanCallEdges;
9671
+ try {
9672
+ gcOrphanEmbeddings = database.gcOrphanEmbeddings();
9673
+ gcOrphanChunks = database.gcOrphanChunks();
9674
+ gcOrphanSymbols = database.gcOrphanSymbols();
9675
+ gcOrphanCallEdges = database.gcOrphanCallEdges();
9676
+ } catch (error) {
9677
+ if (!await this.tryResetCorruptedIndex("running index health check", error)) {
9678
+ throw error;
9679
+ }
9680
+ await this.ensureInitialized();
9681
+ return {
9682
+ removed: 0,
9683
+ filePaths: [],
9684
+ gcOrphanEmbeddings: 0,
9685
+ gcOrphanChunks: 0,
9686
+ gcOrphanSymbols: 0,
9687
+ gcOrphanCallEdges: 0,
9688
+ resetCorruptedIndex: true,
9689
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9690
+ };
9691
+ }
8296
9692
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
8297
9693
  this.logger.gc("info", "Health check complete", {
8298
9694
  removedStale: removedCount,
@@ -8303,8 +9699,12 @@ var Indexer = class {
8303
9699
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8304
9700
  }
8305
9701
  async retryFailedBatches() {
8306
- const { store, provider, invertedIndex } = await this.ensureInitialized();
8307
- const failedBatches = this.loadFailedBatches();
9702
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9703
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
9704
+ const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
9705
+ const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
9706
+ const { scoped: scopedFailedBatches, retained: retainedFailedBatches } = roots ? this.partitionFailedBatches(roots, maxChunkTokens) : { scoped: this.loadFailedBatches(maxChunkTokens), retained: [] };
9707
+ const failedBatches = scopedFailedBatches;
8308
9708
  if (failedBatches.length === 0) {
8309
9709
  return { succeeded: 0, failed: 0, remaining: 0 };
8310
9710
  }
@@ -8312,49 +9712,170 @@ var Indexer = class {
8312
9712
  let failed = 0;
8313
9713
  const stillFailing = [];
8314
9714
  for (const batch of failedBatches) {
9715
+ const batchChunksById = new Map(batch.chunks.map((chunk) => [chunk.id, chunk]));
9716
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9717
+ const completedChunkIds = /* @__PURE__ */ new Set();
9718
+ const failedChunkIds = /* @__PURE__ */ new Set();
9719
+ const failedChunksForBatch = /* @__PURE__ */ new Map();
9720
+ const pooledResults = [];
8315
9721
  try {
8316
- const result = await pRetry(
8317
- async () => {
8318
- const texts = batch.chunks.map((c) => c.text);
8319
- return provider.embedBatch(texts);
8320
- },
8321
- {
8322
- retries: this.config.indexing.retries,
8323
- minTimeout: this.config.indexing.retryDelayMs
8324
- }
9722
+ const requestBatches = createPendingEmbeddingRequestBatches(
9723
+ batch.chunks,
9724
+ getDynamicBatchOptions(configuredProviderInfo)
8325
9725
  );
8326
- const items = batch.chunks.map((chunk, idx) => ({
9726
+ for (const requestBatch of requestBatches) {
9727
+ try {
9728
+ const result = await pRetry(
9729
+ async () => {
9730
+ const texts = requestBatch.map((request) => request.text);
9731
+ return provider.embedBatch(texts);
9732
+ },
9733
+ {
9734
+ retries: this.config.indexing.retries,
9735
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
9736
+ maxTimeout: providerRateLimits.maxRetryMs,
9737
+ factor: 2,
9738
+ shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError)
9739
+ }
9740
+ );
9741
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9742
+ requestBatch.forEach((request, idx) => {
9743
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9744
+ return;
9745
+ }
9746
+ const vector = result.embeddings[idx];
9747
+ if (!vector) {
9748
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9749
+ }
9750
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9751
+ parts[request.partIndex] = {
9752
+ vector,
9753
+ tokenCount: request.tokenCount
9754
+ };
9755
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9756
+ touchedChunkIds.add(request.chunk.id);
9757
+ });
9758
+ for (const chunkId of touchedChunkIds) {
9759
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9760
+ continue;
9761
+ }
9762
+ const chunk = batchChunksById.get(chunkId);
9763
+ if (!chunk) {
9764
+ continue;
9765
+ }
9766
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9767
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9768
+ continue;
9769
+ }
9770
+ const orderedParts = parts;
9771
+ pooledResults.push({
9772
+ chunk,
9773
+ vector: poolEmbeddingVectors(
9774
+ orderedParts.map((part) => part.vector),
9775
+ orderedParts.map((part) => part.tokenCount)
9776
+ )
9777
+ });
9778
+ }
9779
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
9780
+ } catch (error) {
9781
+ const failureMessage = String(error);
9782
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9783
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id) && !failedChunkIds.has(chunk.id));
9784
+ for (const chunk of failedChunks) {
9785
+ failedChunkIds.add(chunk.id);
9786
+ embeddingPartsByChunk.delete(chunk.id);
9787
+ failedChunksForBatch.set(chunk.id, {
9788
+ chunks: [chunk],
9789
+ attemptCount: batch.attemptCount + 1,
9790
+ lastAttempt: failureTimestamp,
9791
+ error: failureMessage
9792
+ });
9793
+ }
9794
+ failed += failedChunks.length;
9795
+ this.logger.recordEmbeddingError();
9796
+ }
9797
+ }
9798
+ const successfulResults = pooledResults.filter(({ chunk }) => !failedChunkIds.has(chunk.id));
9799
+ const items = successfulResults.map(({ chunk, vector }) => ({
8327
9800
  id: chunk.id,
8328
- vector: result.embeddings[idx],
9801
+ vector,
8329
9802
  metadata: chunk.metadata
8330
9803
  }));
8331
- store.addBatch(items);
8332
- for (const chunk of batch.chunks) {
9804
+ if (items.length > 0) {
9805
+ store.addBatch(items);
9806
+ }
9807
+ if (successfulResults.length > 0) {
9808
+ try {
9809
+ database.upsertEmbeddingsBatch(
9810
+ successfulResults.map(({ chunk, vector }) => ({
9811
+ contentHash: chunk.contentHash,
9812
+ embedding: float32ArrayToBuffer(vector),
9813
+ chunkText: chunk.storageText,
9814
+ model: configuredProviderInfo.modelInfo.model
9815
+ }))
9816
+ );
9817
+ } catch (dbError) {
9818
+ this.rebuildVectorStoreExcludingChunkIds(
9819
+ store,
9820
+ database,
9821
+ successfulResults.map(({ chunk }) => chunk.id)
9822
+ );
9823
+ throw dbError;
9824
+ }
9825
+ }
9826
+ for (const { chunk } of successfulResults) {
8333
9827
  invertedIndex.removeChunk(chunk.id);
8334
9828
  invertedIndex.addChunk(chunk.id, chunk.content);
9829
+ completedChunkIds.add(chunk.id);
9830
+ embeddingPartsByChunk.delete(chunk.id);
8335
9831
  }
8336
- this.logger.recordChunksEmbedded(batch.chunks.length);
8337
- this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
8338
- succeeded += batch.chunks.length;
9832
+ database.addChunksToBranchBatch(
9833
+ this.getBranchCatalogKey(),
9834
+ successfulResults.map(({ chunk }) => chunk.id)
9835
+ );
9836
+ this.logger.recordChunksEmbedded(successfulResults.length);
9837
+ succeeded += successfulResults.length;
9838
+ stillFailing.push(...failedChunksForBatch.values());
8339
9839
  } catch (error) {
8340
- failed += batch.chunks.length;
9840
+ const failureMessage = getErrorMessage(error);
9841
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9842
+ const unaccountedChunks = batch.chunks.filter(
9843
+ (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
9844
+ );
9845
+ for (const chunk of unaccountedChunks) {
9846
+ failedChunksForBatch.set(chunk.id, {
9847
+ chunks: [chunk],
9848
+ attemptCount: batch.attemptCount + 1,
9849
+ lastAttempt: failureTimestamp,
9850
+ error: failureMessage
9851
+ });
9852
+ }
9853
+ failed += unaccountedChunks.length;
8341
9854
  this.logger.recordEmbeddingError();
8342
- stillFailing.push({
8343
- ...batch,
8344
- attemptCount: batch.attemptCount + 1,
8345
- lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
8346
- error: String(error)
8347
- });
9855
+ stillFailing.push(...coalesceFailedBatches(Array.from(failedChunksForBatch.values())));
8348
9856
  }
8349
9857
  }
8350
- this.saveFailedBatches(stillFailing);
9858
+ const persistedStillFailing = coalesceFailedBatches(stillFailing);
9859
+ if (roots) {
9860
+ this.saveFailedBatches([...retainedFailedBatches, ...persistedStillFailing]);
9861
+ } else {
9862
+ this.saveFailedBatches(persistedStillFailing);
9863
+ }
8351
9864
  if (succeeded > 0) {
8352
9865
  store.save();
8353
9866
  invertedIndex.save();
8354
9867
  }
8355
- return { succeeded, failed, remaining: stillFailing.length };
9868
+ if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
9869
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9870
+ this.saveIndexMetadata(configuredProviderInfo);
9871
+ this.indexCompatibility = { compatible: true };
9872
+ }
9873
+ return { succeeded, failed, remaining: persistedStillFailing.length };
8356
9874
  }
8357
9875
  getFailedBatchesCount() {
9876
+ if (this.config.scope === "global") {
9877
+ return this.partitionFailedBatches(this.getScopedRoots()).scoped.length;
9878
+ }
8358
9879
  return this.loadFailedBatches().length;
8359
9880
  }
8360
9881
  getCurrentBranch() {
@@ -8403,20 +9924,23 @@ var Indexer = class {
8403
9924
  const semanticResults = store.search(embedding, limit * 2);
8404
9925
  const vectorMs = performance2.now() - vectorStartTime;
8405
9926
  let branchChunkIds = null;
8406
- if (filterByBranch && this.currentBranch !== "default") {
8407
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9927
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9928
+ branchChunkIds = new Set(
9929
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9930
+ );
8408
9931
  }
8409
9932
  const prefilterStartTime = performance2.now();
8410
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9933
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9934
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8411
9935
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8412
- const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9936
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
8413
9937
  const prefilterMs = performance2.now() - prefilterStartTime;
8414
- if (branchChunkIds && branchChunkIds.size === 0) {
9938
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8415
9939
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8416
9940
  branch: this.currentBranch
8417
9941
  });
8418
9942
  }
8419
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9943
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8420
9944
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8421
9945
  branch: this.currentBranch
8422
9946
  });
@@ -8489,11 +10013,39 @@ var Indexer = class {
8489
10013
  }
8490
10014
  async getCallers(targetName) {
8491
10015
  const { database } = await this.ensureInitialized();
8492
- return database.getCallersWithContext(targetName, this.currentBranch);
10016
+ const seen = /* @__PURE__ */ new Set();
10017
+ const results = [];
10018
+ for (const branchKey of this.getBranchCatalogKeys()) {
10019
+ for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10020
+ if (!seen.has(edge.id)) {
10021
+ seen.add(edge.id);
10022
+ results.push(edge);
10023
+ }
10024
+ }
10025
+ }
10026
+ return results;
8493
10027
  }
8494
10028
  async getCallees(symbolId) {
8495
10029
  const { database } = await this.ensureInitialized();
8496
- return database.getCallees(symbolId, this.currentBranch);
10030
+ const seen = /* @__PURE__ */ new Set();
10031
+ const results = [];
10032
+ for (const branchKey of this.getBranchCatalogKeys()) {
10033
+ for (const edge of database.getCallees(symbolId, branchKey)) {
10034
+ if (!seen.has(edge.id)) {
10035
+ seen.add(edge.id);
10036
+ results.push(edge);
10037
+ }
10038
+ }
10039
+ }
10040
+ return results;
10041
+ }
10042
+ async close() {
10043
+ await this.database?.close();
10044
+ this.database = null;
10045
+ this.store = null;
10046
+ this.invertedIndex = null;
10047
+ this.provider = null;
10048
+ this.reranker = null;
8497
10049
  }
8498
10050
  };
8499
10051
 
@@ -8506,7 +10058,17 @@ function truncateContent(content) {
8506
10058
  // ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
8507
10059
  }
8508
10060
  function formatIndexStats(stats, verbose = false) {
10061
+ if (stats.resetCorruptedIndex) {
10062
+ return stats.warning ?? "Detected a corrupted local index and reset it during indexing. Run index_codebase again to rebuild search data.";
10063
+ }
8509
10064
  const lines = [];
10065
+ if (stats.failedChunks > 0) {
10066
+ lines.push(`INDEXING WARNING: ${stats.failedChunks} chunks failed to embed.`);
10067
+ if (stats.failedBatchesPath) {
10068
+ lines.push(`Inspect failed batches at: ${stats.failedBatchesPath}`);
10069
+ }
10070
+ lines.push("");
10071
+ }
8510
10072
  if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
8511
10073
  lines.push(`${stats.totalFiles} files processed, ${stats.existingChunks} code chunks already up to date.`);
8512
10074
  } else if (stats.indexedChunks === 0) {
@@ -8551,6 +10113,19 @@ function formatIndexStats(stats, verbose = false) {
8551
10113
  }
8552
10114
  function formatStatus(status) {
8553
10115
  if (!status.indexed) {
10116
+ if (status.warning) {
10117
+ return status.warning;
10118
+ }
10119
+ if (status.failedBatchesCount > 0) {
10120
+ const lines2 = [
10121
+ "Codebase is not indexed. The last indexing run left failed embedding batches.",
10122
+ "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."
10123
+ ];
10124
+ if (status.failedBatchesPath) {
10125
+ lines2.push(`Failed batches: ${status.failedBatchesPath}`);
10126
+ }
10127
+ return lines2.join("\n");
10128
+ }
8554
10129
  return "Codebase is not indexed. Run index_codebase to create an index.";
8555
10130
  }
8556
10131
  const lines = [
@@ -8563,6 +10138,13 @@ function formatStatus(status) {
8563
10138
  lines.push(`Current branch: ${status.currentBranch}`);
8564
10139
  lines.push(`Base branch: ${status.baseBranch}`);
8565
10140
  }
10141
+ if (status.failedBatchesCount > 0) {
10142
+ lines.push("");
10143
+ lines.push(`INDEXING WARNING: ${status.failedBatchesCount} failed embedding batch${status.failedBatchesCount === 1 ? " remains" : "es remain"}.`);
10144
+ if (status.failedBatchesPath) {
10145
+ lines.push(`Failed batches: ${status.failedBatchesPath}`);
10146
+ }
10147
+ }
8566
10148
  if (status.compatibility && !status.compatibility.compatible) {
8567
10149
  lines.push("");
8568
10150
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8620,6 +10202,9 @@ function formatCodebasePeek(results) {
8620
10202
  return formatted.join("\n");
8621
10203
  }
8622
10204
  function formatHealthCheck(result) {
10205
+ if (result.resetCorruptedIndex) {
10206
+ return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
10207
+ }
8623
10208
  if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
8624
10209
  return "Index is healthy. No stale entries found.";
8625
10210
  }
@@ -8679,8 +10264,8 @@ ${truncateContent(r.content)}
8679
10264
  }
8680
10265
 
8681
10266
  // src/tools/index.ts
8682
- import { existsSync as existsSync6, writeFileSync as writeFileSync2, mkdirSync, statSync as statSync2 } from "fs";
8683
- import * as path8 from "path";
10267
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, statSync as statSync2 } from "fs";
10268
+ import * as path9 from "path";
8684
10269
  var z = tool.schema;
8685
10270
  var sharedIndexer = null;
8686
10271
  var sharedProjectRoot = "";
@@ -8695,7 +10280,20 @@ function refreshIndexerFromConfig() {
8695
10280
  if (!sharedProjectRoot) {
8696
10281
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
8697
10282
  }
8698
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadConfig()));
10283
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10284
+ }
10285
+ function shouldForceLocalizeProjectIndex() {
10286
+ const currentConfig = parseConfig(loadRuntimeConfig());
10287
+ if (currentConfig.scope !== "project") {
10288
+ return false;
10289
+ }
10290
+ const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10291
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10292
+ if (!mainRepoRoot) {
10293
+ return false;
10294
+ }
10295
+ const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10296
+ return !existsSync7(localIndexPath) && existsSync7(inheritedIndexPath);
8699
10297
  }
8700
10298
  function getIndexer() {
8701
10299
  if (!sharedIndexer) {
@@ -8704,9 +10302,41 @@ function getIndexer() {
8704
10302
  return sharedIndexer;
8705
10303
  }
8706
10304
  function getConfigPath() {
8707
- return path8.join(sharedProjectRoot, ".opencode", "codebase-index.json");
10305
+ return resolveWritableProjectConfigPath(sharedProjectRoot);
8708
10306
  }
8709
- function loadConfig() {
10307
+ function normalizeConfigPathValue(value, baseDir) {
10308
+ const trimmed = value.trim();
10309
+ if (!trimmed) {
10310
+ return trimmed;
10311
+ }
10312
+ const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10313
+ return path9.normalize(absolutePath);
10314
+ }
10315
+ function serializeConfigPathValue(value, baseDir) {
10316
+ const trimmed = value.trim();
10317
+ if (!trimmed) {
10318
+ return trimmed;
10319
+ }
10320
+ const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10321
+ if (!path9.isAbsolute(trimmed)) {
10322
+ return normalizeRelativePath(path9.normalize(trimmed));
10323
+ }
10324
+ const relativePath = path9.relative(baseDir, trimmed);
10325
+ if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10326
+ return normalizeRelativePath(path9.normalize(relativePath || "."));
10327
+ }
10328
+ return path9.normalize(trimmed);
10329
+ }
10330
+ function normalizeKnowledgeBasePaths(config) {
10331
+ const normalized = { ...config };
10332
+ if (Array.isArray(normalized.knowledgeBases)) {
10333
+ normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10334
+ return normalizeConfigPathValue(kb, sharedProjectRoot);
10335
+ });
10336
+ }
10337
+ return normalized;
10338
+ }
10339
+ function loadRuntimeConfig() {
8710
10340
  const rawConfig = loadMergedConfig(sharedProjectRoot);
8711
10341
  const config = {};
8712
10342
  if (rawConfig && typeof rawConfig === "object") {
@@ -8714,27 +10344,32 @@ function loadConfig() {
8714
10344
  config[key] = rawConfig[key];
8715
10345
  }
8716
10346
  }
8717
- if (Array.isArray(config.knowledgeBases)) {
8718
- config.knowledgeBases = config.knowledgeBases.map((kb) => {
8719
- const resolved = path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb);
8720
- return path8.normalize(resolved);
8721
- });
8722
- }
8723
- if (Array.isArray(config.additionalInclude)) {
8724
- config.additionalInclude = config.additionalInclude.map((pattern) => {
8725
- const resolved = path8.isAbsolute(pattern) ? pattern : path8.resolve(sharedProjectRoot, pattern);
8726
- return path8.normalize(resolved);
8727
- });
10347
+ return normalizeKnowledgeBasePaths(config);
10348
+ }
10349
+ function loadEditableConfig() {
10350
+ const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10351
+ const config = {};
10352
+ if (rawConfig && typeof rawConfig === "object") {
10353
+ for (const key of Object.keys(rawConfig)) {
10354
+ config[key] = rawConfig[key];
10355
+ }
8728
10356
  }
8729
- return config;
10357
+ return normalizeKnowledgeBasePaths(config);
8730
10358
  }
8731
10359
  function saveConfig(config) {
8732
- const configDir = path8.join(sharedProjectRoot, ".opencode");
8733
- if (!existsSync6(configDir)) {
8734
- mkdirSync(configDir, { recursive: true });
8735
- }
8736
10360
  const configPath = getConfigPath();
8737
- writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
10361
+ const configDir = path9.dirname(configPath);
10362
+ const configBaseDir = path9.dirname(configDir);
10363
+ if (!existsSync7(configDir)) {
10364
+ mkdirSync2(configDir, { recursive: true });
10365
+ }
10366
+ const serializableConfig = { ...config };
10367
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
10368
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10369
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
10370
+ );
10371
+ }
10372
+ writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
8738
10373
  }
8739
10374
  var codebase_peek = tool({
8740
10375
  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.",
@@ -8764,12 +10399,17 @@ var index_codebase = tool({
8764
10399
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
8765
10400
  },
8766
10401
  async execute(args, context) {
8767
- const indexer = getIndexer();
10402
+ let indexer = getIndexer();
8768
10403
  if (args.estimateOnly) {
8769
10404
  const estimate = await indexer.estimateCost();
8770
10405
  return formatCostEstimate(estimate);
8771
10406
  }
8772
10407
  if (args.force) {
10408
+ if (shouldForceLocalizeProjectIndex()) {
10409
+ materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10410
+ refreshIndexerFromConfig();
10411
+ indexer = getIndexer();
10412
+ }
8773
10413
  await indexer.clearIndex();
8774
10414
  }
8775
10415
  const stats = await indexer.index((progress) => {
@@ -8950,8 +10590,8 @@ var add_knowledge_base = tool({
8950
10590
  },
8951
10591
  async execute(args) {
8952
10592
  const inputPath = args.path.trim();
8953
- const resolvedPath = path8.isAbsolute(inputPath) ? inputPath : path8.resolve(sharedProjectRoot, inputPath);
8954
- if (!existsSync6(resolvedPath)) {
10593
+ const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10594
+ if (!existsSync7(resolvedPath)) {
8955
10595
  return `Error: Directory does not exist: ${resolvedPath}`;
8956
10596
  }
8957
10597
  try {
@@ -8962,11 +10602,11 @@ var add_knowledge_base = tool({
8962
10602
  } catch (error) {
8963
10603
  return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
8964
10604
  }
8965
- const config = loadConfig();
10605
+ const config = loadEditableConfig();
8966
10606
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
8967
- const normalizedPath = path8.normalize(resolvedPath);
10607
+ const normalizedPath = path9.normalize(resolvedPath);
8968
10608
  const alreadyExists = knowledgeBases.some(
8969
- (kb) => path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedPath
10609
+ (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
8970
10610
  );
8971
10611
  if (alreadyExists) {
8972
10612
  return `Knowledge base already configured: ${resolvedPath}`;
@@ -8990,7 +10630,7 @@ var list_knowledge_bases = tool({
8990
10630
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
8991
10631
  args: {},
8992
10632
  async execute() {
8993
- const config = loadConfig();
10633
+ const config = loadRuntimeConfig();
8994
10634
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
8995
10635
  if (knowledgeBases.length === 0) {
8996
10636
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -9000,8 +10640,8 @@ var list_knowledge_bases = tool({
9000
10640
  `;
9001
10641
  for (let i = 0; i < knowledgeBases.length; i++) {
9002
10642
  const kb = knowledgeBases[i];
9003
- const resolvedPath = path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb);
9004
- const exists = existsSync6(resolvedPath);
10643
+ const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10644
+ const exists = existsSync7(resolvedPath);
9005
10645
  result += `[${i + 1}] ${kb}
9006
10646
  `;
9007
10647
  result += ` Resolved: ${resolvedPath}
@@ -9029,14 +10669,14 @@ var remove_knowledge_base = tool({
9029
10669
  },
9030
10670
  async execute(args) {
9031
10671
  const inputPath = args.path.trim();
9032
- const config = loadConfig();
10672
+ const config = loadEditableConfig();
9033
10673
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9034
10674
  if (knowledgeBases.length === 0) {
9035
10675
  return "No knowledge bases configured.";
9036
10676
  }
9037
- const normalizedInput = path8.normalize(inputPath);
10677
+ const normalizedInput = path9.normalize(inputPath);
9038
10678
  const index = knowledgeBases.findIndex(
9039
- (kb) => path8.normalize(kb) === normalizedInput || path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedInput
10679
+ (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
9040
10680
  );
9041
10681
  if (index === -1) {
9042
10682
  let result2 = `Knowledge base not found: ${inputPath}
@@ -9068,8 +10708,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
9068
10708
  });
9069
10709
 
9070
10710
  // src/commands/loader.ts
9071
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
9072
- import * as path9 from "path";
10711
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
10712
+ import * as path10 from "path";
9073
10713
  function parseFrontmatter(content) {
9074
10714
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
9075
10715
  const match = content.match(frontmatterRegex);
@@ -9090,15 +10730,15 @@ function parseFrontmatter(content) {
9090
10730
  }
9091
10731
  function loadCommandsFromDirectory(commandsDir) {
9092
10732
  const commands = /* @__PURE__ */ new Map();
9093
- if (!existsSync7(commandsDir)) {
10733
+ if (!existsSync8(commandsDir)) {
9094
10734
  return commands;
9095
10735
  }
9096
10736
  const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
9097
10737
  for (const file of files) {
9098
- const filePath = path9.join(commandsDir, file);
10738
+ const filePath = path10.join(commandsDir, file);
9099
10739
  const content = readFileSync6(filePath, "utf-8");
9100
10740
  const { frontmatter, body } = parseFrontmatter(content);
9101
- const name = path9.basename(file, ".md");
10741
+ const name = path10.basename(file, ".md");
9102
10742
  const description = frontmatter.description || `Run the ${name} command`;
9103
10743
  commands.set(name, {
9104
10744
  description,
@@ -9385,9 +11025,9 @@ function replaceActiveWatcher(nextWatcher) {
9385
11025
  function getCommandsDir() {
9386
11026
  let currentDir = process.cwd();
9387
11027
  if (typeof import.meta !== "undefined" && import.meta.url) {
9388
- currentDir = path10.dirname(fileURLToPath2(import.meta.url));
11028
+ currentDir = path11.dirname(fileURLToPath2(import.meta.url));
9389
11029
  }
9390
- return path10.join(currentDir, "..", "commands");
11030
+ return path11.join(currentDir, "..", "commands");
9391
11031
  }
9392
11032
  var plugin = async ({ directory }) => {
9393
11033
  try {