opencode-codebase-index 0.7.0 → 0.8.1

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 dirname9 = sp.dirname(file);
2105
+ const basename5 = sp.basename(file);
2106
+ const parent = this.fsw._getWatchedDir(dirname9);
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(dirname9, 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, mkdirSync as mkdirSync2, 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,455 @@ var Indexer = class {
7061
7640
  }
7062
7641
  atomicWriteSync(targetPath, data) {
7063
7642
  const tempPath = `${targetPath}.tmp`;
7064
- writeFileSync(tempPath, data);
7643
+ mkdirSync2(path8.dirname(targetPath), { recursive: true });
7644
+ writeFileSync2(tempPath, data);
7065
7645
  renameSync(tempPath, targetPath);
7066
7646
  }
7067
- checkForInterruptedIndexing() {
7068
- return existsSync5(this.indexingLockPath);
7647
+ getScopedRoots() {
7648
+ const roots = /* @__PURE__ */ new Set([path8.resolve(this.projectRoot)]);
7649
+ for (const kbRoot of this.config.knowledgeBases) {
7650
+ roots.add(path8.resolve(this.projectRoot, kbRoot));
7651
+ }
7652
+ return Array.from(roots);
7069
7653
  }
7070
- acquireIndexingLock() {
7071
- const lockData = {
7072
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7073
- pid: process.pid
7074
- };
7075
- writeFileSync(this.indexingLockPath, JSON.stringify(lockData));
7654
+ getBranchCatalogKey() {
7655
+ const branchName = this.currentBranch || "default";
7656
+ if (this.config.scope !== "global") {
7657
+ return branchName;
7658
+ }
7659
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7660
+ return `${projectHash}:${branchName}`;
7076
7661
  }
7077
- releaseIndexingLock() {
7078
- if (existsSync5(this.indexingLockPath)) {
7079
- unlinkSync(this.indexingLockPath);
7662
+ getLegacyBranchCatalogKey() {
7663
+ return this.currentBranch || "default";
7664
+ }
7665
+ getLegacyMigrationMetadataKey() {
7666
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7667
+ return `index.globalBranchMigration.${projectHash}`;
7668
+ }
7669
+ getProjectEmbeddingStrategyMetadataKey() {
7670
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7671
+ return `index.embeddingStrategyVersion.${projectHash}`;
7672
+ }
7673
+ getProjectForceReembedMetadataKey() {
7674
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7675
+ return `index.forceReembed.${projectHash}`;
7676
+ }
7677
+ hasProjectForceReembedPending() {
7678
+ return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7679
+ }
7680
+ hasScopedIndexedData() {
7681
+ if (!this.store || this.config.scope !== "global") {
7682
+ return false;
7683
+ }
7684
+ if (this.hasProjectForceReembedPending()) {
7685
+ return false;
7686
+ }
7687
+ const roots = this.getScopedRoots();
7688
+ if (Array.from(this.fileHashCache.keys()).some((filePath) => this.isFileInCurrentScope(filePath, roots))) {
7689
+ return true;
7690
+ }
7691
+ if (this.loadSerializedFailedBatches().some(
7692
+ (batch) => batch.chunks.some((chunk) => {
7693
+ const filePath = getPendingChunkFilePath(chunk);
7694
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7695
+ })
7696
+ )) {
7697
+ return true;
7698
+ }
7699
+ if (!this.database) {
7700
+ return false;
7701
+ }
7702
+ if (this.getBranchCatalogKeys().some((branchKey) => {
7703
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7704
+ if (branchChunkIds.length > 0) {
7705
+ return true;
7706
+ }
7707
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7708
+ })) {
7709
+ return true;
7080
7710
  }
7711
+ const hasAnyBranchRows = this.database.getAllBranches().some((branchKey) => {
7712
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7713
+ if (branchChunkIds.length > 0) {
7714
+ return true;
7715
+ }
7716
+ return this.database.getBranchSymbolIds(branchKey).length > 0;
7717
+ });
7718
+ if (hasAnyBranchRows) {
7719
+ return false;
7720
+ }
7721
+ return this.store.getAllMetadata().some(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7081
7722
  }
7082
- async recoverFromInterruptedIndexing() {
7083
- this.logger.warn("Detected interrupted indexing session, recovering...");
7084
- if (existsSync5(this.fileHashCachePath)) {
7085
- unlinkSync(this.fileHashCachePath);
7723
+ loadStoredEmbeddingStrategyVersion() {
7724
+ if (!this.database) {
7725
+ return null;
7086
7726
  }
7087
- await this.healthCheck();
7088
- this.releaseIndexingLock();
7089
- this.logger.info("Recovery complete, next index will re-process all files");
7727
+ if (this.hasProjectForceReembedPending()) {
7728
+ return null;
7729
+ }
7730
+ if (this.config.scope !== "global") {
7731
+ return this.database.getMetadata("index.embeddingStrategyVersion") ?? "1";
7732
+ }
7733
+ const projectVersion = this.database.getMetadata(this.getProjectEmbeddingStrategyMetadataKey());
7734
+ if (projectVersion) {
7735
+ return projectVersion;
7736
+ }
7737
+ const legacySharedVersion = this.database.getMetadata("index.embeddingStrategyVersion");
7738
+ if (legacySharedVersion && this.hasScopedIndexedData()) {
7739
+ return legacySharedVersion;
7740
+ }
7741
+ return null;
7090
7742
  }
7091
- loadFailedBatches() {
7092
- try {
7093
- if (existsSync5(this.failedBatchesPath)) {
7094
- const data = readFileSync5(this.failedBatchesPath, "utf-8");
7095
- return JSON.parse(data);
7743
+ getBranchCatalogKeys() {
7744
+ const primary = this.getBranchCatalogKey();
7745
+ if (this.config.scope !== "global") {
7746
+ return [primary];
7747
+ }
7748
+ if (this.database?.getMetadata(this.getLegacyMigrationMetadataKey()) === "done") {
7749
+ return [primary];
7750
+ }
7751
+ const legacy = this.getLegacyBranchCatalogKey();
7752
+ return primary === legacy ? [primary] : [primary, legacy];
7753
+ }
7754
+ getBranchCatalogCleanupKeys() {
7755
+ const primary = this.getBranchCatalogKey();
7756
+ if (this.config.scope !== "global") {
7757
+ return [primary];
7758
+ }
7759
+ const legacy = this.getLegacyBranchCatalogKey();
7760
+ return primary === legacy ? [primary] : [primary, legacy];
7761
+ }
7762
+ getProjectLocalScopedOwnershipIds(roots) {
7763
+ const chunkIds = /* @__PURE__ */ new Set();
7764
+ const symbolIds = /* @__PURE__ */ new Set();
7765
+ if (!this.database) {
7766
+ return { chunkIds, symbolIds };
7767
+ }
7768
+ const projectRootPath = path8.resolve(this.projectRoot);
7769
+ const projectLocalFilePaths = /* @__PURE__ */ new Set([
7770
+ ...Array.from(this.fileHashCache.keys()).filter(
7771
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7772
+ ),
7773
+ ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
7774
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
7775
+ )
7776
+ ]);
7777
+ for (const filePath of projectLocalFilePaths) {
7778
+ for (const chunk of this.database.getChunksByFile(filePath)) {
7779
+ chunkIds.add(chunk.chunkId);
7780
+ }
7781
+ for (const symbol of this.database.getSymbolsByFile(filePath)) {
7782
+ symbolIds.add(symbol.id);
7096
7783
  }
7097
- } catch {
7098
- return [];
7099
7784
  }
7100
- return [];
7785
+ return { chunkIds, symbolIds };
7101
7786
  }
7102
- saveFailedBatches(batches) {
7103
- if (batches.length === 0) {
7104
- if (existsSync5(this.failedBatchesPath)) {
7105
- fsPromises2.unlink(this.failedBatchesPath).catch(() => {
7106
- });
7787
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
7788
+ if (this.config.scope !== "global") {
7789
+ return this.getBranchCatalogCleanupKeys();
7790
+ }
7791
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7792
+ const keys = /* @__PURE__ */ new Set();
7793
+ const projectChunkIdSet = new Set(projectChunkIds);
7794
+ const projectSymbolIdSet = new Set(projectSymbolIds);
7795
+ for (const branchKey of this.database?.getAllBranches() ?? []) {
7796
+ if (branchKey.startsWith(`${projectHash}:`)) {
7797
+ keys.add(branchKey);
7798
+ continue;
7107
7799
  }
7108
- return;
7800
+ if (branchKey.includes(":")) {
7801
+ continue;
7802
+ }
7803
+ const referencesProjectChunks = this.database?.getBranchChunkIds(branchKey).some((chunkId) => projectChunkIdSet.has(chunkId)) ?? false;
7804
+ const referencesProjectSymbols = this.database?.getBranchSymbolIds(branchKey).some((symbolId) => projectSymbolIdSet.has(symbolId)) ?? false;
7805
+ if (referencesProjectChunks || referencesProjectSymbols) {
7806
+ keys.add(branchKey);
7807
+ }
7808
+ }
7809
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
7810
+ keys.add(branchKey);
7109
7811
  }
7110
- writeFileSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7812
+ return Array.from(keys);
7111
7813
  }
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);
7814
+ isFileInCurrentScope(filePath, roots) {
7815
+ return roots.some((root) => isPathWithinRoot(filePath, root));
7121
7816
  }
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
- };
7817
+ clearScopedFileHashCache(roots) {
7818
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7819
+ if (this.isFileInCurrentScope(filePath, roots)) {
7820
+ this.fileHashCache.delete(filePath);
7140
7821
  }
7141
- default:
7142
- return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
7143
7822
  }
7823
+ this.saveFileHashCache();
7144
7824
  }
7145
- async rerankCandidatesWithApi(query, candidates, options) {
7146
- const reranker = this.config.reranker;
7147
- if (!reranker || !reranker.enabled || candidates.length <= 1) {
7148
- return candidates;
7825
+ replaceScopedFileHashCache(currentFileHashes, roots) {
7826
+ for (const filePath of Array.from(this.fileHashCache.keys())) {
7827
+ if (this.isFileInCurrentScope(filePath, roots)) {
7828
+ this.fileHashCache.delete(filePath);
7829
+ }
7149
7830
  }
7150
- const queryTokens = Array.from(tokenizeTextForRanking(query));
7151
- const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
7152
- const docIntent = classifyDocIntent(queryTokens) === "docs";
7831
+ for (const [filePath, hash] of currentFileHashes) {
7832
+ this.fileHashCache.set(filePath, hash);
7833
+ }
7834
+ this.saveFileHashCache();
7835
+ }
7836
+ partitionFailedBatches(roots, maxChunkTokens) {
7837
+ const scoped = [];
7838
+ const retained = [];
7839
+ for (const batch of this.loadSerializedFailedBatches()) {
7840
+ const scopedChunks = batch.chunks.filter((chunk) => {
7841
+ const filePath = getPendingChunkFilePath(chunk);
7842
+ return filePath !== null && this.isFileInCurrentScope(filePath, roots);
7843
+ });
7844
+ const retainedChunks = batch.chunks.filter((chunk) => {
7845
+ const filePath = getPendingChunkFilePath(chunk);
7846
+ return filePath === null || !this.isFileInCurrentScope(filePath, roots);
7847
+ });
7848
+ if (scopedChunks.length > 0) {
7849
+ const normalizedBatch = normalizeFailedBatch({ ...batch, chunks: scopedChunks }, maxChunkTokens);
7850
+ if (normalizedBatch) {
7851
+ scoped.push(normalizedBatch);
7852
+ }
7853
+ }
7854
+ if (retainedChunks.length > 0) {
7855
+ retained.push({ ...batch, chunks: retainedChunks });
7856
+ }
7857
+ }
7858
+ return { scoped, retained };
7859
+ }
7860
+ clearScopedFailedBatches(roots) {
7861
+ const { retained: retainedBatches } = this.partitionFailedBatches(roots);
7862
+ this.saveFailedBatches(retainedBatches);
7863
+ }
7864
+ hasForeignScopedFileHashData(roots) {
7865
+ return Array.from(this.fileHashCache.keys()).some((filePath) => !this.isFileInCurrentScope(filePath, roots));
7866
+ }
7867
+ hasForeignScopedFailedBatches(roots) {
7868
+ const { retained } = this.partitionFailedBatches(roots);
7869
+ return retained.length > 0;
7870
+ }
7871
+ hasForeignScopedBranchData() {
7872
+ if (!this.database || this.config.scope !== "global") {
7873
+ return false;
7874
+ }
7875
+ const projectHash = hashContent(path8.resolve(this.projectRoot)).slice(0, 16);
7876
+ const roots = this.getScopedRoots();
7877
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7878
+ return this.database.getAllBranches().some(
7879
+ (branchKey) => {
7880
+ const branchChunkIds = this.database.getBranchChunkIds(branchKey);
7881
+ const branchSymbolIds = this.database.getBranchSymbolIds(branchKey);
7882
+ const hasBranchData = branchChunkIds.length > 0 || branchSymbolIds.length > 0;
7883
+ if (!hasBranchData) {
7884
+ return false;
7885
+ }
7886
+ if (branchKey.startsWith(`${projectHash}:`)) {
7887
+ return false;
7888
+ }
7889
+ if (!branchKey.includes(":")) {
7890
+ const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
7891
+ const referencesCurrentProjectSymbols = branchSymbolIds.some((symbolId) => projectLocalSymbolIds.has(symbolId));
7892
+ return !(referencesCurrentProjectChunks || referencesCurrentProjectSymbols);
7893
+ }
7894
+ return true;
7895
+ }
7896
+ );
7897
+ }
7898
+ saveScopedFailedBatches(batches, roots) {
7899
+ const { retained } = this.partitionFailedBatches(roots);
7900
+ this.saveFailedBatches([...retained, ...batches]);
7901
+ }
7902
+ clearSharedIndexProjectData(store, invertedIndex, database, roots) {
7903
+ const allMetadata = store.getAllMetadata();
7904
+ const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
7905
+ const filePaths = /* @__PURE__ */ new Set([
7906
+ ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7907
+ ...scopedEntries.map(({ metadata }) => metadata.filePath)
7908
+ ]);
7909
+ const projectRootPath = path8.resolve(this.projectRoot);
7910
+ const projectLocalFilePaths = new Set(
7911
+ Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7912
+ );
7913
+ const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
7914
+ for (const filePath of filePaths) {
7915
+ for (const chunk of database.getChunksByFile(filePath)) {
7916
+ removedChunkIds.add(chunk.chunkId);
7917
+ }
7918
+ }
7919
+ const removedChunkIdList = Array.from(removedChunkIds);
7920
+ const projectLocalChunkIds = new Set(
7921
+ scopedEntries.filter(({ metadata }) => isPathWithinRoot(metadata.filePath, projectRootPath)).map(({ key }) => key)
7922
+ );
7923
+ for (const filePath of projectLocalFilePaths) {
7924
+ for (const chunk of database.getChunksByFile(filePath)) {
7925
+ projectLocalChunkIds.add(chunk.chunkId);
7926
+ }
7927
+ }
7928
+ const symbolIds = [];
7929
+ const projectLocalSymbolIds = /* @__PURE__ */ new Set();
7930
+ for (const filePath of filePaths) {
7931
+ for (const symbol of database.getSymbolsByFile(filePath)) {
7932
+ symbolIds.push(symbol.id);
7933
+ if (projectLocalFilePaths.has(filePath)) {
7934
+ projectLocalSymbolIds.add(symbol.id);
7935
+ }
7936
+ }
7937
+ }
7938
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7939
+ database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
7940
+ }
7941
+ const sharedChunkIds = new Set(database.getReferencedChunkIds(removedChunkIdList));
7942
+ const removableChunkIds = removedChunkIdList.filter((chunkId) => !sharedChunkIds.has(chunkId));
7943
+ if (removableChunkIds.length > 0) {
7944
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removableChunkIds);
7945
+ for (const chunkId of removableChunkIds) {
7946
+ invertedIndex.removeChunk(chunkId);
7947
+ }
7948
+ }
7949
+ for (const branchKey of this.getProjectScopedBranchCatalogCleanupKeys(Array.from(projectLocalChunkIds), Array.from(projectLocalSymbolIds))) {
7950
+ database.deleteBranchSymbolsForBranch(branchKey, symbolIds);
7951
+ }
7952
+ const sharedSymbolIds = new Set(database.getReferencedSymbolIds(symbolIds));
7953
+ const removableSymbolIds = symbolIds.filter((symbolId) => !sharedSymbolIds.has(symbolId));
7954
+ database.clearCallEdgeTargetsForSymbols(removableSymbolIds);
7955
+ for (const filePath of filePaths) {
7956
+ const fileChunkIds = database.getChunksByFile(filePath).map((chunk) => chunk.chunkId);
7957
+ const fileSymbols = database.getSymbolsByFile(filePath);
7958
+ if (fileChunkIds.every((chunkId) => !sharedChunkIds.has(chunkId))) {
7959
+ database.deleteChunksByFile(filePath);
7960
+ }
7961
+ if (fileSymbols.every((symbol) => !sharedSymbolIds.has(symbol.id))) {
7962
+ database.deleteCallEdgesByFile(filePath);
7963
+ database.deleteSymbolsByFile(filePath);
7964
+ }
7965
+ }
7966
+ database.gcOrphanCallEdges();
7967
+ database.gcOrphanSymbols();
7968
+ database.gcOrphanEmbeddings();
7969
+ database.gcOrphanChunks();
7970
+ store.save();
7971
+ invertedIndex.save();
7972
+ return {
7973
+ removedChunkIds: removedChunkIdList,
7974
+ hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
7975
+ };
7976
+ }
7977
+ checkForInterruptedIndexing() {
7978
+ return existsSync6(this.indexingLockPath);
7979
+ }
7980
+ acquireIndexingLock() {
7981
+ const lockData = {
7982
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7983
+ pid: process.pid
7984
+ };
7985
+ writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
7986
+ }
7987
+ releaseIndexingLock() {
7988
+ if (existsSync6(this.indexingLockPath)) {
7989
+ unlinkSync(this.indexingLockPath);
7990
+ }
7991
+ }
7992
+ async recoverFromInterruptedIndexing() {
7993
+ this.logger.warn("Detected interrupted indexing session, recovering...");
7994
+ if (existsSync6(this.fileHashCachePath)) {
7995
+ unlinkSync(this.fileHashCachePath);
7996
+ }
7997
+ await this.healthCheck();
7998
+ this.releaseIndexingLock();
7999
+ this.logger.info("Recovery complete, next index will re-process all files");
8000
+ }
8001
+ loadFailedBatches(maxChunkTokens) {
8002
+ try {
8003
+ return this.loadSerializedFailedBatches().map((batch) => normalizeFailedBatch(batch, maxChunkTokens)).filter((batch) => batch !== null);
8004
+ } catch {
8005
+ return [];
8006
+ }
8007
+ }
8008
+ loadSerializedFailedBatches() {
8009
+ if (!existsSync6(this.failedBatchesPath)) {
8010
+ return [];
8011
+ }
8012
+ const data = readFileSync5(this.failedBatchesPath, "utf-8");
8013
+ const parsed = JSON.parse(data);
8014
+ return parsed.map((batch) => {
8015
+ const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
8016
+ if (chunks.length === 0) {
8017
+ return null;
8018
+ }
8019
+ return {
8020
+ chunks,
8021
+ error: typeof batch.error === "string" ? batch.error : "Unknown embedding error",
8022
+ attemptCount: typeof batch.attemptCount === "number" ? batch.attemptCount : 1,
8023
+ lastAttempt: typeof batch.lastAttempt === "string" ? batch.lastAttempt : (/* @__PURE__ */ new Date()).toISOString()
8024
+ };
8025
+ }).filter((batch) => batch !== null);
8026
+ }
8027
+ saveFailedBatches(batches) {
8028
+ if (batches.length === 0) {
8029
+ if (existsSync6(this.failedBatchesPath)) {
8030
+ try {
8031
+ unlinkSync(this.failedBatchesPath);
8032
+ } catch {
8033
+ }
8034
+ }
8035
+ return;
8036
+ }
8037
+ writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
8038
+ }
8039
+ collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
8040
+ const retryableById = /* @__PURE__ */ new Map();
8041
+ for (const batch of this.loadFailedBatches(maxChunkTokens)) {
8042
+ for (const chunk of batch.chunks) {
8043
+ const filePath = chunk.metadata.filePath;
8044
+ if (!currentFileHashes.has(filePath)) {
8045
+ continue;
8046
+ }
8047
+ if (!unchangedFilePaths.has(filePath)) {
8048
+ continue;
8049
+ }
8050
+ const existing = retryableById.get(chunk.id);
8051
+ if (!existing || batch.attemptCount > existing.attemptCount) {
8052
+ retryableById.set(chunk.id, {
8053
+ chunk,
8054
+ attemptCount: batch.attemptCount
8055
+ });
8056
+ }
8057
+ }
8058
+ }
8059
+ return Array.from(retryableById.values());
8060
+ }
8061
+ getProviderRateLimits(provider) {
8062
+ switch (provider) {
8063
+ case "github-copilot":
8064
+ return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
8065
+ case "openai":
8066
+ return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
8067
+ case "google":
8068
+ return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
8069
+ case "ollama":
8070
+ return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
8071
+ case "custom": {
8072
+ const customConfig = this.config.customProvider;
8073
+ return {
8074
+ concurrency: customConfig?.concurrency ?? 3,
8075
+ intervalMs: customConfig?.requestIntervalMs ?? 1e3,
8076
+ minRetryMs: 1e3,
8077
+ maxRetryMs: 3e4
8078
+ };
8079
+ }
8080
+ default:
8081
+ return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
8082
+ }
8083
+ }
8084
+ async rerankCandidatesWithApi(query, candidates, options) {
8085
+ const reranker = this.config.reranker;
8086
+ if (!reranker || !reranker.enabled || candidates.length <= 1) {
8087
+ return candidates;
8088
+ }
8089
+ const queryTokens = Array.from(tokenizeTextForRanking(query));
8090
+ const preferSourcePaths = classifyQueryIntentRaw(query) === "source";
8091
+ const docIntent = classifyDocIntent(queryTokens) === "docs";
7153
8092
  if (options?.definitionIntent === true) {
7154
8093
  return candidates;
7155
8094
  }
@@ -7322,31 +8261,54 @@ var Indexer = class {
7322
8261
  }
7323
8262
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
7324
8263
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7325
- const storePath = path7.join(this.indexPath, "vectors");
8264
+ const storePath = path8.join(this.indexPath, "vectors");
7326
8265
  this.store = new VectorStore(storePath, dimensions);
7327
- const indexFilePath = path7.join(this.indexPath, "vectors.usearch");
7328
- if (existsSync5(indexFilePath)) {
8266
+ const indexFilePath = path8.join(this.indexPath, "vectors.usearch");
8267
+ if (existsSync6(indexFilePath)) {
7329
8268
  this.store.load();
7330
8269
  }
7331
- const invertedIndexPath = path7.join(this.indexPath, "inverted-index.json");
8270
+ const invertedIndexPath = path8.join(this.indexPath, "inverted-index.json");
7332
8271
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7333
8272
  try {
7334
8273
  this.invertedIndex.load();
7335
8274
  } catch {
7336
- if (existsSync5(invertedIndexPath)) {
8275
+ if (existsSync6(invertedIndexPath)) {
7337
8276
  await fsPromises2.unlink(invertedIndexPath);
7338
8277
  }
7339
8278
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7340
8279
  }
7341
- const dbPath = path7.join(this.indexPath, "codebase.db");
7342
- const dbIsNew = !existsSync5(dbPath);
7343
- this.database = new Database(dbPath);
8280
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8281
+ let dbIsNew = !existsSync6(dbPath);
8282
+ try {
8283
+ this.database = new Database(dbPath);
8284
+ } catch (error) {
8285
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
8286
+ throw error;
8287
+ }
8288
+ this.store = new VectorStore(storePath, dimensions);
8289
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
8290
+ this.database = new Database(dbPath);
8291
+ dbIsNew = true;
8292
+ }
8293
+ if (isGitRepo(this.projectRoot)) {
8294
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
8295
+ this.baseBranch = getBaseBranch(this.projectRoot);
8296
+ this.logger.branch("info", "Detected git repository", {
8297
+ currentBranch: this.currentBranch,
8298
+ baseBranch: this.baseBranch
8299
+ });
8300
+ } else {
8301
+ this.currentBranch = "default";
8302
+ this.baseBranch = "default";
8303
+ this.logger.branch("debug", "Not a git repository, using default branch");
8304
+ }
7344
8305
  if (this.checkForInterruptedIndexing()) {
7345
8306
  await this.recoverFromInterruptedIndexing();
7346
8307
  }
7347
8308
  if (dbIsNew && this.store.count() > 0) {
7348
8309
  this.migrateFromLegacyIndex();
7349
8310
  }
8311
+ this.loadFileHashCache();
7350
8312
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7351
8313
  if (!this.indexCompatibility.compatible) {
7352
8314
  this.logger.warn("Index compatibility issue detected", {
@@ -7355,18 +8317,6 @@ var Indexer = class {
7355
8317
  configuredProviderInfo: this.configuredProviderInfo
7356
8318
  });
7357
8319
  }
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
8320
  if (this.config.indexing.autoGc) {
7371
8321
  await this.maybeRunAutoGc();
7372
8322
  }
@@ -7386,20 +8336,169 @@ var Indexer = class {
7386
8336
  }
7387
8337
  }
7388
8338
  if (shouldRunGc) {
7389
- await this.healthCheck();
8339
+ const result = await this.healthCheck();
8340
+ if (result.warning) {
8341
+ this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
8342
+ } else {
8343
+ this.database.deleteMetadata(STARTUP_WARNING_METADATA_KEY);
8344
+ }
7390
8345
  this.database.setMetadata("lastGcTimestamp", now.toString());
7391
8346
  }
7392
8347
  }
7393
8348
  async maybeRunOrphanGc() {
7394
- if (!this.database) return;
8349
+ if (!this.database) return null;
7395
8350
  const stats = this.database.getStats();
7396
- if (!stats) return;
8351
+ if (!stats) return null;
7397
8352
  const orphanCount = stats.embeddingCount - stats.chunkCount;
7398
8353
  if (orphanCount > this.config.indexing.gcOrphanThreshold) {
7399
- this.database.gcOrphanEmbeddings();
7400
- this.database.gcOrphanChunks();
8354
+ try {
8355
+ this.database.gcOrphanEmbeddings();
8356
+ this.database.gcOrphanChunks();
8357
+ } catch (error) {
8358
+ if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
8359
+ return {
8360
+ resetCorruptedIndex: true,
8361
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
8362
+ };
8363
+ }
8364
+ throw error;
8365
+ }
7401
8366
  this.database.setMetadata("lastGcTimestamp", Date.now().toString());
7402
8367
  }
8368
+ return null;
8369
+ }
8370
+ rebuildVectorStoreExcludingChunkIds(store, database, excludedChunkIds) {
8371
+ const excludedSet = new Set(excludedChunkIds);
8372
+ if (excludedSet.size === 0) {
8373
+ return;
8374
+ }
8375
+ const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
8376
+ const storeBasePath = path8.join(this.indexPath, "vectors");
8377
+ const storeIndexPath = `${storeBasePath}.usearch`;
8378
+ const storeMetadataPath = `${storeBasePath}.meta.json`;
8379
+ const backupIndexPath = `${storeIndexPath}.bak`;
8380
+ const backupMetadataPath = `${storeMetadataPath}.bak`;
8381
+ let backedUpIndex = false;
8382
+ let backedUpMetadata = false;
8383
+ let rebuiltCount = 0;
8384
+ let skippedCount = 0;
8385
+ if (existsSync6(backupIndexPath)) {
8386
+ unlinkSync(backupIndexPath);
8387
+ }
8388
+ if (existsSync6(backupMetadataPath)) {
8389
+ unlinkSync(backupMetadataPath);
8390
+ }
8391
+ try {
8392
+ if (existsSync6(storeIndexPath)) {
8393
+ renameSync(storeIndexPath, backupIndexPath);
8394
+ backedUpIndex = true;
8395
+ }
8396
+ if (existsSync6(storeMetadataPath)) {
8397
+ renameSync(storeMetadataPath, backupMetadataPath);
8398
+ backedUpMetadata = true;
8399
+ }
8400
+ store.clear();
8401
+ for (const { key, metadata } of retainedEntries) {
8402
+ const chunk = database.getChunk(key);
8403
+ if (!chunk) {
8404
+ skippedCount += 1;
8405
+ continue;
8406
+ }
8407
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8408
+ if (!embeddingBuffer) {
8409
+ skippedCount += 1;
8410
+ continue;
8411
+ }
8412
+ const vector = bufferToFloat32Array(embeddingBuffer);
8413
+ store.add(key, Array.from(vector), metadata);
8414
+ rebuiltCount += 1;
8415
+ }
8416
+ store.save();
8417
+ if (backedUpIndex && existsSync6(backupIndexPath)) {
8418
+ unlinkSync(backupIndexPath);
8419
+ }
8420
+ if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8421
+ unlinkSync(backupMetadataPath);
8422
+ }
8423
+ this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
8424
+ excludedChunks: excludedSet.size,
8425
+ rebuiltChunks: rebuiltCount,
8426
+ skippedChunks: skippedCount
8427
+ });
8428
+ } catch (error) {
8429
+ try {
8430
+ store.clear();
8431
+ } catch {
8432
+ }
8433
+ if (existsSync6(storeIndexPath)) {
8434
+ unlinkSync(storeIndexPath);
8435
+ }
8436
+ if (existsSync6(storeMetadataPath)) {
8437
+ unlinkSync(storeMetadataPath);
8438
+ }
8439
+ if (backedUpIndex && existsSync6(backupIndexPath)) {
8440
+ renameSync(backupIndexPath, storeIndexPath);
8441
+ }
8442
+ if (backedUpMetadata && existsSync6(backupMetadataPath)) {
8443
+ renameSync(backupMetadataPath, storeMetadataPath);
8444
+ }
8445
+ if (backedUpIndex || backedUpMetadata) {
8446
+ store.load();
8447
+ }
8448
+ throw error;
8449
+ }
8450
+ }
8451
+ getCorruptedIndexWarning(dbPath) {
8452
+ if (this.config.scope === "global") {
8453
+ 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.`;
8454
+ }
8455
+ return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
8456
+ }
8457
+ async tryResetCorruptedIndex(stage, error) {
8458
+ if (!isSqliteCorruptionError(error)) {
8459
+ return false;
8460
+ }
8461
+ const dbPath = path8.join(this.indexPath, "codebase.db");
8462
+ const warning = this.getCorruptedIndexWarning(dbPath);
8463
+ const errorMessage = getErrorMessage(error);
8464
+ if (this.config.scope === "global") {
8465
+ this.logger.error("Detected corrupted shared global index database", {
8466
+ stage,
8467
+ dbPath,
8468
+ error: errorMessage
8469
+ });
8470
+ throw new Error(`${warning} Original SQLite error: ${errorMessage}`);
8471
+ }
8472
+ this.logger.warn("Detected corrupted local index database, resetting local index", {
8473
+ stage,
8474
+ dbPath,
8475
+ error: errorMessage
8476
+ });
8477
+ this.store = null;
8478
+ this.invertedIndex = null;
8479
+ this.database?.close();
8480
+ this.database = null;
8481
+ this.indexCompatibility = null;
8482
+ this.fileHashCache.clear();
8483
+ const resetPaths = [
8484
+ path8.join(this.indexPath, "codebase.db"),
8485
+ path8.join(this.indexPath, "codebase.db-shm"),
8486
+ path8.join(this.indexPath, "codebase.db-wal"),
8487
+ path8.join(this.indexPath, "vectors.usearch"),
8488
+ path8.join(this.indexPath, "inverted-index.json"),
8489
+ path8.join(this.indexPath, "file-hashes.json"),
8490
+ path8.join(this.indexPath, "failed-batches.json"),
8491
+ path8.join(this.indexPath, "indexing.lock"),
8492
+ path8.join(this.indexPath, "vectors")
8493
+ ];
8494
+ await Promise.all(resetPaths.map(async (targetPath) => {
8495
+ try {
8496
+ await fsPromises2.rm(targetPath, { recursive: true, force: true });
8497
+ } catch {
8498
+ }
8499
+ }));
8500
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
8501
+ return true;
7403
8502
  }
7404
8503
  migrateFromLegacyIndex() {
7405
8504
  if (!this.store || !this.database) return;
@@ -7423,7 +8522,7 @@ var Indexer = class {
7423
8522
  if (chunkDataBatch.length > 0) {
7424
8523
  this.database.upsertChunksBatch(chunkDataBatch);
7425
8524
  }
7426
- this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
8525
+ this.database.addChunksToBranchBatch(this.getBranchCatalogKey(), chunkIds);
7427
8526
  }
7428
8527
  loadIndexMetadata() {
7429
8528
  if (!this.database) return null;
@@ -7434,6 +8533,7 @@ var Indexer = class {
7434
8533
  embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
7435
8534
  embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
7436
8535
  embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
8536
+ embeddingStrategyVersion: this.loadStoredEmbeddingStrategyVersion() ?? EMBEDDING_STRATEGY_VERSION,
7437
8537
  createdAt: this.database.getMetadata("index.createdAt") ?? "",
7438
8538
  updatedAt: this.database.getMetadata("index.updatedAt") ?? ""
7439
8539
  };
@@ -7442,10 +8542,22 @@ var Indexer = class {
7442
8542
  if (!this.database) return;
7443
8543
  const now = (/* @__PURE__ */ new Date()).toISOString();
7444
8544
  const existingCreatedAt = this.database.getMetadata("index.createdAt");
8545
+ const completeProjectEmbeddingStrategyReset = !this.hasProjectForceReembedPending();
7445
8546
  this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
7446
8547
  this.database.setMetadata("index.embeddingProvider", provider.provider);
7447
8548
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
7448
8549
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8550
+ if (this.config.scope === "global") {
8551
+ if (completeProjectEmbeddingStrategyReset) {
8552
+ this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
8553
+ }
8554
+ this.database.setMetadata(this.getLegacyMigrationMetadataKey(), "done");
8555
+ if (completeProjectEmbeddingStrategyReset) {
8556
+ this.database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8557
+ }
8558
+ } else {
8559
+ this.database.setMetadata("index.embeddingStrategyVersion", EMBEDDING_STRATEGY_VERSION);
8560
+ }
7449
8561
  this.database.setMetadata("index.updatedAt", now);
7450
8562
  if (!existingCreatedAt) {
7451
8563
  this.database.setMetadata("index.createdAt", now);
@@ -7475,6 +8587,14 @@ var Indexer = class {
7475
8587
  storedMetadata
7476
8588
  };
7477
8589
  }
8590
+ if (storedMetadata.embeddingStrategyVersion !== EMBEDDING_STRATEGY_VERSION) {
8591
+ return {
8592
+ compatible: false,
8593
+ code: "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */,
8594
+ 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.`,
8595
+ storedMetadata
8596
+ };
8597
+ }
7478
8598
  if (storedMetadata.embeddingProvider !== currentProvider) {
7479
8599
  this.logger.warn("Provider changed", {
7480
8600
  storedProvider: storedMetadata.embeddingProvider,
@@ -7522,6 +8642,10 @@ var Indexer = class {
7522
8642
  }
7523
8643
  async index(onProgress) {
7524
8644
  const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8645
+ const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
8646
+ const branchCatalogKey = this.getBranchCatalogKey();
8647
+ const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
8648
+ const failedForcedChunkIds = /* @__PURE__ */ new Set();
7525
8649
  if (!this.indexCompatibility?.compatible) {
7526
8650
  throw new Error(
7527
8651
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
@@ -7543,6 +8667,7 @@ var Indexer = class {
7543
8667
  skippedFiles: [],
7544
8668
  parseFailures: []
7545
8669
  };
8670
+ const failedBatchesForCurrentRun = [];
7546
8671
  onProgress?.({
7547
8672
  phase: "scanning",
7548
8673
  filesProcessed: 0,
@@ -7602,6 +8727,12 @@ var Indexer = class {
7602
8727
  const existingChunks = /* @__PURE__ */ new Map();
7603
8728
  const existingChunksByFile = /* @__PURE__ */ new Map();
7604
8729
  for (const { key, metadata } of store.getAllMetadata()) {
8730
+ if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8731
+ continue;
8732
+ }
8733
+ if (forceScopedReembed && scopedRoots && this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
8734
+ continue;
8735
+ }
7605
8736
  existingChunks.set(key, metadata.hash);
7606
8737
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7607
8738
  fileChunks.add(key);
@@ -7623,7 +8754,7 @@ var Indexer = class {
7623
8754
  for (const parsed of parsedFiles) {
7624
8755
  currentFilePaths.add(parsed.path);
7625
8756
  if (parsed.chunks.length === 0) {
7626
- const relativePath = path7.relative(this.projectRoot, parsed.path);
8757
+ const relativePath = path8.relative(this.projectRoot, parsed.path);
7627
8758
  stats.parseFailures.push(relativePath);
7628
8759
  }
7629
8760
  let fileChunkCount = 0;
@@ -7659,7 +8790,10 @@ var Indexer = class {
7659
8790
  fileChunkCount++;
7660
8791
  continue;
7661
8792
  }
7662
- const text = createEmbeddingText(chunk, parsed.path);
8793
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text) => ({
8794
+ text,
8795
+ tokenCount: estimateTokens(text)
8796
+ }));
7663
8797
  const metadata = {
7664
8798
  filePath: parsed.path,
7665
8799
  startLine: chunk.startLine,
@@ -7669,10 +8803,38 @@ var Indexer = class {
7669
8803
  language: chunk.language,
7670
8804
  hash: contentHash
7671
8805
  };
7672
- pendingChunks.push({ id, text, content: chunk.content, contentHash, metadata });
8806
+ pendingChunks.push({
8807
+ id,
8808
+ texts,
8809
+ storageText: createPendingChunkStorageText(texts),
8810
+ content: chunk.content,
8811
+ contentHash,
8812
+ metadata
8813
+ });
7673
8814
  fileChunkCount++;
7674
8815
  }
7675
8816
  }
8817
+ const retryableFailedChunks = this.collectRetryableFailedChunks(
8818
+ currentFileHashes,
8819
+ unchangedFilePaths,
8820
+ getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)
8821
+ );
8822
+ const retryableFailedAttemptCounts = /* @__PURE__ */ new Map();
8823
+ const retryableChunksWithExistingData = /* @__PURE__ */ new Set();
8824
+ if (retryableFailedChunks.length > 0) {
8825
+ const pendingChunkIds = new Set(pendingChunks.map((chunk) => chunk.id));
8826
+ for (const { chunk, attemptCount } of retryableFailedChunks) {
8827
+ retryableFailedAttemptCounts.set(chunk.id, attemptCount);
8828
+ if (existingChunks.has(chunk.id)) {
8829
+ retryableChunksWithExistingData.add(chunk.id);
8830
+ }
8831
+ if (!pendingChunkIds.has(chunk.id)) {
8832
+ pendingChunks.push(chunk);
8833
+ pendingChunkIds.add(chunk.id);
8834
+ currentChunkIds.add(chunk.id);
8835
+ }
8836
+ }
8837
+ }
7676
8838
  if (chunkDataBatch.length > 0) {
7677
8839
  database.upsertChunksBatch(chunkDataBatch);
7678
8840
  }
@@ -7701,17 +8863,20 @@ var Indexer = class {
7701
8863
  fileSymbols.push(symbol);
7702
8864
  allSymbolIds.add(symbolId);
7703
8865
  }
8866
+ const fileLanguage = parsed.chunks[0]?.language;
8867
+ const isCaseInsensitiveLanguage = !!fileLanguage && CASE_INSENSITIVE_LANGUAGES.has(fileLanguage);
8868
+ const normalizeSymbolKey = (name) => isCaseInsensitiveLanguage ? name.toLowerCase() : name;
7704
8869
  const symbolsByName = /* @__PURE__ */ new Map();
7705
8870
  for (const symbol of fileSymbols) {
7706
- const existing = symbolsByName.get(symbol.name) ?? [];
8871
+ const key = normalizeSymbolKey(symbol.name);
8872
+ const existing = symbolsByName.get(key) ?? [];
7707
8873
  existing.push(symbol);
7708
- symbolsByName.set(symbol.name, existing);
8874
+ symbolsByName.set(key, existing);
7709
8875
  }
7710
8876
  if (fileSymbols.length > 0) {
7711
8877
  database.upsertSymbolsBatch(fileSymbols);
7712
8878
  symbolsByFile.set(parsed.path, fileSymbols);
7713
8879
  }
7714
- const fileLanguage = parsed.chunks[0]?.language;
7715
8880
  if (!fileLanguage || !CALL_GRAPH_LANGUAGES.has(fileLanguage)) continue;
7716
8881
  const callSites = extractCalls(changedFile.content, fileLanguage);
7717
8882
  if (callSites.length === 0) continue;
@@ -7736,7 +8901,7 @@ var Indexer = class {
7736
8901
  if (edges.length > 0) {
7737
8902
  database.upsertCallEdgesBatch(edges);
7738
8903
  for (const edge of edges) {
7739
- const candidates = symbolsByName.get(edge.targetName);
8904
+ const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
7740
8905
  if (candidates && candidates.length === 1) {
7741
8906
  database.resolveCallEdge(edge.id, candidates[0].id);
7742
8907
  }
@@ -7749,14 +8914,20 @@ var Indexer = class {
7749
8914
  allSymbolIds.add(sym.id);
7750
8915
  }
7751
8916
  }
7752
- let removedCount = 0;
8917
+ const removedChunkIds = [];
7753
8918
  for (const [chunkId] of existingChunks) {
7754
8919
  if (!currentChunkIds.has(chunkId)) {
7755
- store.remove(chunkId);
8920
+ removedChunkIds.push(chunkId);
8921
+ }
8922
+ }
8923
+ if (removedChunkIds.length > 0) {
8924
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkIds);
8925
+ for (const chunkId of removedChunkIds) {
7756
8926
  invertedIndex.removeChunk(chunkId);
7757
- removedCount++;
7758
8927
  }
8928
+ database.deleteChunksByIds(removedChunkIds);
7759
8929
  }
8930
+ const removedCount = removedChunkIds.length;
7760
8931
  stats.totalChunks = pendingChunks.length;
7761
8932
  stats.existingChunks = currentChunkIds.size - pendingChunks.length;
7762
8933
  stats.removedChunks = removedCount;
@@ -7768,12 +8939,20 @@ var Indexer = class {
7768
8939
  removed: removedCount
7769
8940
  });
7770
8941
  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();
8942
+ database.clearBranch(branchCatalogKey);
8943
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8944
+ database.clearBranchSymbols(branchCatalogKey);
8945
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8946
+ if (scopedRoots) {
8947
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8948
+ this.clearScopedFailedBatches(scopedRoots);
8949
+ } else {
8950
+ this.fileHashCache = currentFileHashes;
8951
+ this.saveFileHashCache();
8952
+ this.saveFailedBatches([]);
8953
+ }
8954
+ this.saveIndexMetadata(configuredProviderInfo);
8955
+ this.indexCompatibility = { compatible: true };
7777
8956
  stats.durationMs = Date.now() - startTime;
7778
8957
  onProgress?.({
7779
8958
  phase: "complete",
@@ -7786,14 +8965,22 @@ var Indexer = class {
7786
8965
  return stats;
7787
8966
  }
7788
8967
  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));
8968
+ database.clearBranch(branchCatalogKey);
8969
+ database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8970
+ database.clearBranchSymbols(branchCatalogKey);
8971
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7793
8972
  store.save();
7794
8973
  invertedIndex.save();
7795
- this.fileHashCache = currentFileHashes;
7796
- this.saveFileHashCache();
8974
+ if (scopedRoots) {
8975
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
8976
+ this.clearScopedFailedBatches(scopedRoots);
8977
+ } else {
8978
+ this.fileHashCache = currentFileHashes;
8979
+ this.saveFileHashCache();
8980
+ this.saveFailedBatches([]);
8981
+ }
8982
+ this.saveIndexMetadata(configuredProviderInfo);
8983
+ this.indexCompatibility = { compatible: true };
7797
8984
  stats.durationMs = Date.now() - startTime;
7798
8985
  onProgress?.({
7799
8986
  phase: "complete",
@@ -7814,8 +9001,9 @@ var Indexer = class {
7814
9001
  });
7815
9002
  const allContentHashes = pendingChunks.map((c) => c.contentHash);
7816
9003
  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));
9004
+ const forcedReembedChunkIds = forceScopedReembed ? new Set(pendingChunks.map((chunk) => chunk.id)) : /* @__PURE__ */ new Set();
9005
+ const chunksNeedingEmbedding = pendingChunks.filter((c) => forcedReembedChunkIds.has(c.id) || missingHashes.has(c.contentHash));
9006
+ const chunksWithExistingEmbedding = pendingChunks.filter((c) => !forcedReembedChunkIds.has(c.id) && !missingHashes.has(c.contentHash));
7819
9007
  this.logger.cache("info", "Embedding cache lookup", {
7820
9008
  needsEmbedding: chunksNeedingEmbedding.length,
7821
9009
  fromCache: chunksWithExistingEmbedding.length
@@ -7837,17 +9025,24 @@ var Indexer = class {
7837
9025
  interval: providerRateLimits.intervalMs,
7838
9026
  intervalCap: providerRateLimits.concurrency
7839
9027
  });
7840
- const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
9028
+ const pendingChunksById = new Map(chunksNeedingEmbedding.map((chunk) => [chunk.id, chunk]));
9029
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9030
+ const completedChunkIds = /* @__PURE__ */ new Set();
9031
+ const failedChunkIds = /* @__PURE__ */ new Set();
9032
+ const requestBatches = createPendingEmbeddingRequestBatches(
9033
+ chunksNeedingEmbedding,
9034
+ getDynamicBatchOptions(configuredProviderInfo)
9035
+ );
7841
9036
  let rateLimitBackoffMs = 0;
7842
- for (const batch of dynamicBatches) {
9037
+ for (const requestBatch of requestBatches) {
7843
9038
  queue.add(async () => {
7844
9039
  if (rateLimitBackoffMs > 0) {
7845
- await new Promise((resolve7) => setTimeout(resolve7, rateLimitBackoffMs));
9040
+ await new Promise((resolve9) => setTimeout(resolve9, rateLimitBackoffMs));
7846
9041
  }
7847
9042
  try {
7848
9043
  const result = await pRetry(
7849
9044
  async () => {
7850
- const texts = batch.map((c) => c.text);
9045
+ const texts = requestBatch.map((request) => request.text);
7851
9046
  return provider.embedBatch(texts);
7852
9047
  },
7853
9048
  {
@@ -7877,29 +9072,82 @@ var Indexer = class {
7877
9072
  if (rateLimitBackoffMs > 0) {
7878
9073
  rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2e3);
7879
9074
  }
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);
9075
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9076
+ requestBatch.forEach((request, idx) => {
9077
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9078
+ return;
9079
+ }
9080
+ const vector = result.embeddings[idx];
9081
+ if (!vector) {
9082
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9083
+ }
9084
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9085
+ parts[request.partIndex] = {
9086
+ vector,
9087
+ tokenCount: request.tokenCount
9088
+ };
9089
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9090
+ touchedChunkIds.add(request.chunk.id);
9091
+ });
9092
+ const pooledResults = [];
9093
+ for (const chunkId of touchedChunkIds) {
9094
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9095
+ continue;
9096
+ }
9097
+ const chunk = pendingChunksById.get(chunkId);
9098
+ if (!chunk) {
9099
+ continue;
9100
+ }
9101
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9102
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9103
+ continue;
9104
+ }
9105
+ const orderedParts = parts;
9106
+ pooledResults.push({
9107
+ chunk,
9108
+ vector: poolEmbeddingVectors(
9109
+ orderedParts.map((part) => part.vector),
9110
+ orderedParts.map((part) => part.tokenCount)
9111
+ )
9112
+ });
9113
+ }
9114
+ if (pooledResults.length > 0) {
9115
+ const items = pooledResults.map(({ chunk, vector }) => ({
9116
+ id: chunk.id,
9117
+ vector,
9118
+ metadata: chunk.metadata
9119
+ }));
9120
+ store.addBatch(items);
9121
+ const embeddingBatchItems = pooledResults.map(({ chunk, vector }) => ({
9122
+ contentHash: chunk.contentHash,
9123
+ embedding: float32ArrayToBuffer(vector),
9124
+ chunkText: chunk.storageText,
9125
+ model: configuredProviderInfo.modelInfo.model
9126
+ }));
9127
+ try {
9128
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
9129
+ } catch (dbError) {
9130
+ this.rebuildVectorStoreExcludingChunkIds(
9131
+ store,
9132
+ database,
9133
+ pooledResults.map(({ chunk }) => chunk.id)
9134
+ );
9135
+ throw dbError;
9136
+ }
9137
+ for (const { chunk } of pooledResults) {
9138
+ invertedIndex.removeChunk(chunk.id);
9139
+ invertedIndex.addChunk(chunk.id, chunk.content);
9140
+ completedChunkIds.add(chunk.id);
9141
+ embeddingPartsByChunk.delete(chunk.id);
9142
+ }
9143
+ stats.indexedChunks += pooledResults.length;
9144
+ this.logger.recordChunksEmbedded(pooledResults.length);
7896
9145
  }
7897
- stats.indexedChunks += batch.length;
7898
9146
  stats.tokensUsed += result.totalTokensUsed;
7899
- this.logger.recordChunksEmbedded(batch.length);
7900
9147
  this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
7901
9148
  this.logger.embedding("debug", `Embedded batch`, {
7902
- batchSize: batch.length,
9149
+ batchSize: pooledResults.length,
9150
+ requestCount: requestBatch.length,
7903
9151
  tokens: result.totalTokensUsed
7904
9152
  });
7905
9153
  onProgress?.({
@@ -7910,17 +9158,49 @@ var Indexer = class {
7910
9158
  totalChunks: pendingChunks.length
7911
9159
  });
7912
9160
  } catch (error) {
7913
- stats.failedChunks += batch.length;
7914
- this.addFailedBatch(batch, getErrorMessage(error));
9161
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
9162
+ const failureMessage = getErrorMessage(error);
9163
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9164
+ for (const chunk of failedChunks) {
9165
+ if (!failedChunkIds.has(chunk.id)) {
9166
+ failedChunkIds.add(chunk.id);
9167
+ stats.failedChunks += 1;
9168
+ }
9169
+ if (forceScopedReembed) {
9170
+ failedForcedChunkIds.add(chunk.id);
9171
+ }
9172
+ embeddingPartsByChunk.delete(chunk.id);
9173
+ const existingFailedBatchIndex = failedBatchesForCurrentRun.findIndex(
9174
+ (failedBatch2) => failedBatch2.chunks[0]?.id === chunk.id
9175
+ );
9176
+ const existingFailedBatch = existingFailedBatchIndex === -1 ? void 0 : failedBatchesForCurrentRun[existingFailedBatchIndex];
9177
+ const failedBatch = {
9178
+ chunks: [chunk],
9179
+ error: failureMessage,
9180
+ attemptCount: (existingFailedBatch?.attemptCount ?? retryableFailedAttemptCounts.get(chunk.id) ?? 0) + 1,
9181
+ lastAttempt: failureTimestamp
9182
+ };
9183
+ if (existingFailedBatchIndex === -1) {
9184
+ failedBatchesForCurrentRun.push(failedBatch);
9185
+ } else {
9186
+ failedBatchesForCurrentRun[existingFailedBatchIndex] = failedBatch;
9187
+ }
9188
+ }
7915
9189
  this.logger.recordEmbeddingError();
7916
9190
  this.logger.embedding("error", `Failed to embed batch after retries`, {
7917
- batchSize: batch.length,
7918
- error: getErrorMessage(error)
9191
+ batchSize: failedChunks.length,
9192
+ requestCount: requestBatch.length,
9193
+ error: failureMessage
7919
9194
  });
7920
9195
  }
7921
9196
  });
7922
9197
  }
7923
9198
  await queue.onIdle();
9199
+ if (scopedRoots) {
9200
+ this.saveScopedFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun), scopedRoots);
9201
+ } else {
9202
+ this.saveFailedBatches(coalesceFailedBatches(failedBatchesForCurrentRun));
9203
+ }
7924
9204
  onProgress?.({
7925
9205
  phase: "storing",
7926
9206
  filesProcessed: files.length,
@@ -7928,18 +9208,48 @@ var Indexer = class {
7928
9208
  chunksProcessed: stats.indexedChunks,
7929
9209
  totalChunks: pendingChunks.length
7930
9210
  });
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));
9211
+ const branchChunkIds = Array.from(currentChunkIds).filter(
9212
+ (chunkId) => {
9213
+ const isNewlyFailed = failedChunkIds.has(chunkId) && !retryableChunksWithExistingData.has(chunkId);
9214
+ const isForcedFailed = forceScopedReembed && failedForcedChunkIds.has(chunkId);
9215
+ return !isNewlyFailed && !isForcedFailed;
9216
+ }
9217
+ );
9218
+ database.clearBranch(branchCatalogKey);
9219
+ database.addChunksToBranchBatch(branchCatalogKey, branchChunkIds);
9220
+ database.clearBranchSymbols(branchCatalogKey);
9221
+ database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7935
9222
  store.save();
7936
9223
  invertedIndex.save();
7937
- this.fileHashCache = currentFileHashes;
7938
- this.saveFileHashCache();
9224
+ if (scopedRoots) {
9225
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
9226
+ } else {
9227
+ this.fileHashCache = currentFileHashes;
9228
+ this.saveFileHashCache();
9229
+ }
7939
9230
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
7940
- await this.maybeRunOrphanGc();
9231
+ const gcReset = await this.maybeRunOrphanGc();
9232
+ if (gcReset) {
9233
+ stats.durationMs = Date.now() - startTime;
9234
+ stats.warning = gcReset.warning;
9235
+ stats.resetCorruptedIndex = true;
9236
+ this.logger.recordIndexingEnd();
9237
+ this.logger.warn("Indexing ended after resetting corrupted local index during automatic GC", {
9238
+ files: stats.totalFiles,
9239
+ indexed: stats.indexedChunks,
9240
+ existing: stats.existingChunks,
9241
+ removed: stats.removedChunks,
9242
+ failed: stats.failedChunks,
9243
+ tokens: stats.tokensUsed,
9244
+ durationMs: stats.durationMs
9245
+ });
9246
+ return stats;
9247
+ }
7941
9248
  }
7942
9249
  stats.durationMs = Date.now() - startTime;
9250
+ if (forceScopedReembed && failedForcedChunkIds.size === 0) {
9251
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9252
+ }
7943
9253
  this.saveIndexMetadata(configuredProviderInfo);
7944
9254
  this.indexCompatibility = { compatible: true };
7945
9255
  this.logger.recordIndexingEnd();
@@ -8068,27 +9378,30 @@ var Indexer = class {
8068
9378
  const keywordResults = await this.keywordSearch(query, maxResults * 4);
8069
9379
  const keywordMs = performance2.now() - keywordStartTime;
8070
9380
  let branchChunkIds = null;
8071
- if (filterByBranch && this.currentBranch !== "default") {
8072
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9381
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9382
+ branchChunkIds = new Set(
9383
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9384
+ );
8073
9385
  }
8074
9386
  const prefilterStartTime = performance2.now();
8075
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9387
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9388
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8076
9389
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8077
9390
  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;
9391
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9392
+ const keywordCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
8080
9393
  const prefilterMs = performance2.now() - prefilterStartTime;
8081
- if (branchChunkIds && branchChunkIds.size === 0) {
9394
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8082
9395
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8083
9396
  branch: this.currentBranch
8084
9397
  });
8085
9398
  }
8086
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9399
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8087
9400
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8088
9401
  branch: this.currentBranch
8089
9402
  });
8090
9403
  }
8091
- if (shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
9404
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
8092
9405
  this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
8093
9406
  branch: this.currentBranch
8094
9407
  });
@@ -8141,26 +9454,13 @@ var Indexer = class {
8141
9454
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
8142
9455
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
8143
9456
  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
- });
9457
+ const baseFiltered = tiered.filter((r) => matchesSearchFilters(r, options, this.config.search.minScore));
8159
9458
  const implementationOnly = baseFiltered.filter(
8160
9459
  (r) => isLikelyImplementationPath(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
8161
9460
  );
8162
9461
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
8163
- const finalResults = filtered;
9462
+ 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) : [];
9463
+ const finalResults = filtered.length > 0 ? filtered : identifierFallback;
8164
9464
  const totalSearchMs = performance2.now() - searchStartTime;
8165
9465
  this.logger.recordSearch(totalSearchMs, {
8166
9466
  embeddingMs,
@@ -8230,7 +9530,8 @@ var Indexer = class {
8230
9530
  return results.slice(0, limit);
8231
9531
  }
8232
9532
  async getStatus() {
8233
- const { store, configuredProviderInfo } = await this.ensureInitialized();
9533
+ const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9534
+ const failedBatchesCount = this.getFailedBatchesCount();
8234
9535
  return {
8235
9536
  indexed: store.count() > 0,
8236
9537
  vectorCount: store.count(),
@@ -8239,23 +9540,86 @@ var Indexer = class {
8239
9540
  indexPath: this.indexPath,
8240
9541
  currentBranch: this.currentBranch,
8241
9542
  baseBranch: this.baseBranch,
8242
- compatibility: this.indexCompatibility
9543
+ compatibility: this.indexCompatibility,
9544
+ failedBatchesCount,
9545
+ failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
9546
+ warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
8243
9547
  };
8244
9548
  }
8245
9549
  async clearIndex() {
8246
9550
  const { store, invertedIndex, database } = await this.ensureInitialized();
9551
+ if (this.config.scope === "global") {
9552
+ store.load();
9553
+ invertedIndex.load();
9554
+ this.loadFileHashCache();
9555
+ const roots = this.getScopedRoots();
9556
+ const compatibility = this.checkCompatibility();
9557
+ const allMetadata = store.getAllMetadata();
9558
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
9559
+ if (!compatibility.compatible && hasForeignData) {
9560
+ if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
9561
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9562
+ this.clearScopedFileHashCache(roots);
9563
+ this.clearScopedFailedBatches(roots);
9564
+ database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
9565
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9566
+ this.indexCompatibility = { compatible: true };
9567
+ return;
9568
+ }
9569
+ throw new Error(
9570
+ `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.`
9571
+ );
9572
+ }
9573
+ if (!hasForeignData) {
9574
+ store.clear();
9575
+ store.save();
9576
+ invertedIndex.clear();
9577
+ invertedIndex.save();
9578
+ this.fileHashCache.clear();
9579
+ this.saveFileHashCache();
9580
+ database.clearAllIndexedData();
9581
+ this.saveFailedBatches([]);
9582
+ database.deleteMetadata("index.version");
9583
+ database.deleteMetadata("index.embeddingProvider");
9584
+ database.deleteMetadata("index.embeddingModel");
9585
+ database.deleteMetadata("index.embeddingDimensions");
9586
+ database.deleteMetadata("index.embeddingStrategyVersion");
9587
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9588
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9589
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
9590
+ database.deleteMetadata("index.createdAt");
9591
+ database.deleteMetadata("index.updatedAt");
9592
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
9593
+ return;
9594
+ }
9595
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
9596
+ this.clearScopedFileHashCache(roots);
9597
+ this.clearScopedFailedBatches(roots);
9598
+ this.indexCompatibility = compatibility;
9599
+ return;
9600
+ }
9601
+ const localProjectIndexPath = path8.join(this.projectRoot, ".opencode", "index");
9602
+ if (path8.resolve(this.indexPath) !== path8.resolve(localProjectIndexPath)) {
9603
+ throw new Error(
9604
+ "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
9605
+ );
9606
+ }
8247
9607
  store.clear();
8248
9608
  store.save();
8249
9609
  invertedIndex.clear();
8250
9610
  invertedIndex.save();
8251
9611
  this.fileHashCache.clear();
8252
9612
  this.saveFileHashCache();
8253
- database.clearBranch(this.currentBranch);
8254
- database.clearBranchSymbols(this.currentBranch);
9613
+ database.clearAllIndexedData();
9614
+ this.saveFailedBatches([]);
8255
9615
  database.deleteMetadata("index.version");
8256
9616
  database.deleteMetadata("index.embeddingProvider");
8257
9617
  database.deleteMetadata("index.embeddingModel");
8258
9618
  database.deleteMetadata("index.embeddingDimensions");
9619
+ database.deleteMetadata("index.embeddingStrategyVersion");
9620
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
9621
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9622
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey());
8259
9623
  database.deleteMetadata("index.createdAt");
8260
9624
  database.deleteMetadata("index.updatedAt");
8261
9625
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
@@ -8271,28 +9635,61 @@ var Indexer = class {
8271
9635
  filePathsToChunkKeys.set(metadata.filePath, existing);
8272
9636
  }
8273
9637
  const removedFilePaths = [];
8274
- let removedCount = 0;
9638
+ const removedChunkKeys = [];
9639
+ const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8275
9640
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8276
- if (!existsSync5(filePath)) {
9641
+ if (!existsSync6(filePath)) {
9642
+ chunkKeysByRemovedFile.set(filePath, chunkKeys);
8277
9643
  for (const key of chunkKeys) {
8278
- store.remove(key);
8279
- invertedIndex.removeChunk(key);
8280
- removedCount++;
9644
+ removedChunkKeys.push(key);
8281
9645
  }
8282
- database.deleteChunksByFile(filePath);
8283
- database.deleteCallEdgesByFile(filePath);
8284
- database.deleteSymbolsByFile(filePath);
8285
9646
  removedFilePaths.push(filePath);
8286
9647
  }
8287
9648
  }
9649
+ if (removedChunkKeys.length > 0) {
9650
+ this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkKeys);
9651
+ for (const key of removedChunkKeys) {
9652
+ invertedIndex.removeChunk(key);
9653
+ }
9654
+ }
9655
+ for (const filePath of removedFilePaths) {
9656
+ const fileChunkKeys = chunkKeysByRemovedFile.get(filePath) ?? [];
9657
+ if (fileChunkKeys.length > 0) {
9658
+ database.deleteChunksByIds(fileChunkKeys);
9659
+ }
9660
+ database.deleteCallEdgesByFile(filePath);
9661
+ database.deleteSymbolsByFile(filePath);
9662
+ }
9663
+ const removedCount = removedChunkKeys.length;
8288
9664
  if (removedCount > 0) {
8289
9665
  store.save();
8290
9666
  invertedIndex.save();
8291
9667
  }
8292
- const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
8293
- const gcOrphanChunks = database.gcOrphanChunks();
8294
- const gcOrphanSymbols = database.gcOrphanSymbols();
8295
- const gcOrphanCallEdges = database.gcOrphanCallEdges();
9668
+ let gcOrphanEmbeddings;
9669
+ let gcOrphanChunks;
9670
+ let gcOrphanSymbols;
9671
+ let gcOrphanCallEdges;
9672
+ try {
9673
+ gcOrphanEmbeddings = database.gcOrphanEmbeddings();
9674
+ gcOrphanChunks = database.gcOrphanChunks();
9675
+ gcOrphanSymbols = database.gcOrphanSymbols();
9676
+ gcOrphanCallEdges = database.gcOrphanCallEdges();
9677
+ } catch (error) {
9678
+ if (!await this.tryResetCorruptedIndex("running index health check", error)) {
9679
+ throw error;
9680
+ }
9681
+ await this.ensureInitialized();
9682
+ return {
9683
+ removed: 0,
9684
+ filePaths: [],
9685
+ gcOrphanEmbeddings: 0,
9686
+ gcOrphanChunks: 0,
9687
+ gcOrphanSymbols: 0,
9688
+ gcOrphanCallEdges: 0,
9689
+ resetCorruptedIndex: true,
9690
+ warning: this.getCorruptedIndexWarning(path8.join(this.indexPath, "codebase.db"))
9691
+ };
9692
+ }
8296
9693
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
8297
9694
  this.logger.gc("info", "Health check complete", {
8298
9695
  removedStale: removedCount,
@@ -8303,8 +9700,12 @@ var Indexer = class {
8303
9700
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8304
9701
  }
8305
9702
  async retryFailedBatches() {
8306
- const { store, provider, invertedIndex } = await this.ensureInitialized();
8307
- const failedBatches = this.loadFailedBatches();
9703
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9704
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
9705
+ const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
9706
+ const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
9707
+ const { scoped: scopedFailedBatches, retained: retainedFailedBatches } = roots ? this.partitionFailedBatches(roots, maxChunkTokens) : { scoped: this.loadFailedBatches(maxChunkTokens), retained: [] };
9708
+ const failedBatches = scopedFailedBatches;
8308
9709
  if (failedBatches.length === 0) {
8309
9710
  return { succeeded: 0, failed: 0, remaining: 0 };
8310
9711
  }
@@ -8312,49 +9713,170 @@ var Indexer = class {
8312
9713
  let failed = 0;
8313
9714
  const stillFailing = [];
8314
9715
  for (const batch of failedBatches) {
9716
+ const batchChunksById = new Map(batch.chunks.map((chunk) => [chunk.id, chunk]));
9717
+ const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9718
+ const completedChunkIds = /* @__PURE__ */ new Set();
9719
+ const failedChunkIds = /* @__PURE__ */ new Set();
9720
+ const failedChunksForBatch = /* @__PURE__ */ new Map();
9721
+ const pooledResults = [];
8315
9722
  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
- }
9723
+ const requestBatches = createPendingEmbeddingRequestBatches(
9724
+ batch.chunks,
9725
+ getDynamicBatchOptions(configuredProviderInfo)
8325
9726
  );
8326
- const items = batch.chunks.map((chunk, idx) => ({
9727
+ for (const requestBatch of requestBatches) {
9728
+ try {
9729
+ const result = await pRetry(
9730
+ async () => {
9731
+ const texts = requestBatch.map((request) => request.text);
9732
+ return provider.embedBatch(texts);
9733
+ },
9734
+ {
9735
+ retries: this.config.indexing.retries,
9736
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
9737
+ maxTimeout: providerRateLimits.maxRetryMs,
9738
+ factor: 2,
9739
+ shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError)
9740
+ }
9741
+ );
9742
+ const touchedChunkIds = /* @__PURE__ */ new Set();
9743
+ requestBatch.forEach((request, idx) => {
9744
+ if (failedChunkIds.has(request.chunk.id) || completedChunkIds.has(request.chunk.id)) {
9745
+ return;
9746
+ }
9747
+ const vector = result.embeddings[idx];
9748
+ if (!vector) {
9749
+ throw new Error(`Embedding API returned too few vectors for chunk ${request.chunk.id}`);
9750
+ }
9751
+ const parts = embeddingPartsByChunk.get(request.chunk.id) ?? [];
9752
+ parts[request.partIndex] = {
9753
+ vector,
9754
+ tokenCount: request.tokenCount
9755
+ };
9756
+ embeddingPartsByChunk.set(request.chunk.id, parts);
9757
+ touchedChunkIds.add(request.chunk.id);
9758
+ });
9759
+ for (const chunkId of touchedChunkIds) {
9760
+ if (failedChunkIds.has(chunkId) || completedChunkIds.has(chunkId)) {
9761
+ continue;
9762
+ }
9763
+ const chunk = batchChunksById.get(chunkId);
9764
+ if (!chunk) {
9765
+ continue;
9766
+ }
9767
+ const parts = embeddingPartsByChunk.get(chunk.id) ?? [];
9768
+ if (!hasAllEmbeddingParts(parts, chunk.texts.length)) {
9769
+ continue;
9770
+ }
9771
+ const orderedParts = parts;
9772
+ pooledResults.push({
9773
+ chunk,
9774
+ vector: poolEmbeddingVectors(
9775
+ orderedParts.map((part) => part.vector),
9776
+ orderedParts.map((part) => part.tokenCount)
9777
+ )
9778
+ });
9779
+ }
9780
+ this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
9781
+ } catch (error) {
9782
+ const failureMessage = String(error);
9783
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9784
+ const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id) && !failedChunkIds.has(chunk.id));
9785
+ for (const chunk of failedChunks) {
9786
+ failedChunkIds.add(chunk.id);
9787
+ embeddingPartsByChunk.delete(chunk.id);
9788
+ failedChunksForBatch.set(chunk.id, {
9789
+ chunks: [chunk],
9790
+ attemptCount: batch.attemptCount + 1,
9791
+ lastAttempt: failureTimestamp,
9792
+ error: failureMessage
9793
+ });
9794
+ }
9795
+ failed += failedChunks.length;
9796
+ this.logger.recordEmbeddingError();
9797
+ }
9798
+ }
9799
+ const successfulResults = pooledResults.filter(({ chunk }) => !failedChunkIds.has(chunk.id));
9800
+ const items = successfulResults.map(({ chunk, vector }) => ({
8327
9801
  id: chunk.id,
8328
- vector: result.embeddings[idx],
9802
+ vector,
8329
9803
  metadata: chunk.metadata
8330
9804
  }));
8331
- store.addBatch(items);
8332
- for (const chunk of batch.chunks) {
9805
+ if (items.length > 0) {
9806
+ store.addBatch(items);
9807
+ }
9808
+ if (successfulResults.length > 0) {
9809
+ try {
9810
+ database.upsertEmbeddingsBatch(
9811
+ successfulResults.map(({ chunk, vector }) => ({
9812
+ contentHash: chunk.contentHash,
9813
+ embedding: float32ArrayToBuffer(vector),
9814
+ chunkText: chunk.storageText,
9815
+ model: configuredProviderInfo.modelInfo.model
9816
+ }))
9817
+ );
9818
+ } catch (dbError) {
9819
+ this.rebuildVectorStoreExcludingChunkIds(
9820
+ store,
9821
+ database,
9822
+ successfulResults.map(({ chunk }) => chunk.id)
9823
+ );
9824
+ throw dbError;
9825
+ }
9826
+ }
9827
+ for (const { chunk } of successfulResults) {
8333
9828
  invertedIndex.removeChunk(chunk.id);
8334
9829
  invertedIndex.addChunk(chunk.id, chunk.content);
9830
+ completedChunkIds.add(chunk.id);
9831
+ embeddingPartsByChunk.delete(chunk.id);
8335
9832
  }
8336
- this.logger.recordChunksEmbedded(batch.chunks.length);
8337
- this.logger.recordEmbeddingApiCall(result.totalTokensUsed);
8338
- succeeded += batch.chunks.length;
9833
+ database.addChunksToBranchBatch(
9834
+ this.getBranchCatalogKey(),
9835
+ successfulResults.map(({ chunk }) => chunk.id)
9836
+ );
9837
+ this.logger.recordChunksEmbedded(successfulResults.length);
9838
+ succeeded += successfulResults.length;
9839
+ stillFailing.push(...failedChunksForBatch.values());
8339
9840
  } catch (error) {
8340
- failed += batch.chunks.length;
9841
+ const failureMessage = getErrorMessage(error);
9842
+ const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
9843
+ const unaccountedChunks = batch.chunks.filter(
9844
+ (chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
9845
+ );
9846
+ for (const chunk of unaccountedChunks) {
9847
+ failedChunksForBatch.set(chunk.id, {
9848
+ chunks: [chunk],
9849
+ attemptCount: batch.attemptCount + 1,
9850
+ lastAttempt: failureTimestamp,
9851
+ error: failureMessage
9852
+ });
9853
+ }
9854
+ failed += unaccountedChunks.length;
8341
9855
  this.logger.recordEmbeddingError();
8342
- stillFailing.push({
8343
- ...batch,
8344
- attemptCount: batch.attemptCount + 1,
8345
- lastAttempt: (/* @__PURE__ */ new Date()).toISOString(),
8346
- error: String(error)
8347
- });
9856
+ stillFailing.push(...coalesceFailedBatches(Array.from(failedChunksForBatch.values())));
8348
9857
  }
8349
9858
  }
8350
- this.saveFailedBatches(stillFailing);
9859
+ const persistedStillFailing = coalesceFailedBatches(stillFailing);
9860
+ if (roots) {
9861
+ this.saveFailedBatches([...retainedFailedBatches, ...persistedStillFailing]);
9862
+ } else {
9863
+ this.saveFailedBatches(persistedStillFailing);
9864
+ }
8351
9865
  if (succeeded > 0) {
8352
9866
  store.save();
8353
9867
  invertedIndex.save();
8354
9868
  }
8355
- return { succeeded, failed, remaining: stillFailing.length };
9869
+ if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
9870
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
9871
+ this.saveIndexMetadata(configuredProviderInfo);
9872
+ this.indexCompatibility = { compatible: true };
9873
+ }
9874
+ return { succeeded, failed, remaining: persistedStillFailing.length };
8356
9875
  }
8357
9876
  getFailedBatchesCount() {
9877
+ if (this.config.scope === "global") {
9878
+ return this.partitionFailedBatches(this.getScopedRoots()).scoped.length;
9879
+ }
8358
9880
  return this.loadFailedBatches().length;
8359
9881
  }
8360
9882
  getCurrentBranch() {
@@ -8403,20 +9925,23 @@ var Indexer = class {
8403
9925
  const semanticResults = store.search(embedding, limit * 2);
8404
9926
  const vectorMs = performance2.now() - vectorStartTime;
8405
9927
  let branchChunkIds = null;
8406
- if (filterByBranch && this.currentBranch !== "default") {
8407
- branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
9928
+ if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
9929
+ branchChunkIds = new Set(
9930
+ this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
9931
+ );
8408
9932
  }
8409
9933
  const prefilterStartTime = performance2.now();
8410
- const shouldPrefilterByBranch = branchChunkIds !== null && branchChunkIds.size > 0;
9934
+ const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
9935
+ const allowBranchPrefilterFallback = this.config.scope !== "global";
8411
9936
  const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
8412
- const semanticCandidates = shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
9937
+ const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
8413
9938
  const prefilterMs = performance2.now() - prefilterStartTime;
8414
- if (branchChunkIds && branchChunkIds.size === 0) {
9939
+ if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
8415
9940
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
8416
9941
  branch: this.currentBranch
8417
9942
  });
8418
9943
  }
8419
- if (shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
9944
+ if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
8420
9945
  this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
8421
9946
  branch: this.currentBranch
8422
9947
  });
@@ -8489,11 +10014,39 @@ var Indexer = class {
8489
10014
  }
8490
10015
  async getCallers(targetName) {
8491
10016
  const { database } = await this.ensureInitialized();
8492
- return database.getCallersWithContext(targetName, this.currentBranch);
10017
+ const seen = /* @__PURE__ */ new Set();
10018
+ const results = [];
10019
+ for (const branchKey of this.getBranchCatalogKeys()) {
10020
+ for (const edge of database.getCallersWithContext(targetName, branchKey)) {
10021
+ if (!seen.has(edge.id)) {
10022
+ seen.add(edge.id);
10023
+ results.push(edge);
10024
+ }
10025
+ }
10026
+ }
10027
+ return results;
8493
10028
  }
8494
10029
  async getCallees(symbolId) {
8495
10030
  const { database } = await this.ensureInitialized();
8496
- return database.getCallees(symbolId, this.currentBranch);
10031
+ const seen = /* @__PURE__ */ new Set();
10032
+ const results = [];
10033
+ for (const branchKey of this.getBranchCatalogKeys()) {
10034
+ for (const edge of database.getCallees(symbolId, branchKey)) {
10035
+ if (!seen.has(edge.id)) {
10036
+ seen.add(edge.id);
10037
+ results.push(edge);
10038
+ }
10039
+ }
10040
+ }
10041
+ return results;
10042
+ }
10043
+ async close() {
10044
+ await this.database?.close();
10045
+ this.database = null;
10046
+ this.store = null;
10047
+ this.invertedIndex = null;
10048
+ this.provider = null;
10049
+ this.reranker = null;
8497
10050
  }
8498
10051
  };
8499
10052
 
@@ -8506,7 +10059,17 @@ function truncateContent(content) {
8506
10059
  // ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
8507
10060
  }
8508
10061
  function formatIndexStats(stats, verbose = false) {
10062
+ if (stats.resetCorruptedIndex) {
10063
+ return stats.warning ?? "Detected a corrupted local index and reset it during indexing. Run index_codebase again to rebuild search data.";
10064
+ }
8509
10065
  const lines = [];
10066
+ if (stats.failedChunks > 0) {
10067
+ lines.push(`INDEXING WARNING: ${stats.failedChunks} chunks failed to embed.`);
10068
+ if (stats.failedBatchesPath) {
10069
+ lines.push(`Inspect failed batches at: ${stats.failedBatchesPath}`);
10070
+ }
10071
+ lines.push("");
10072
+ }
8510
10073
  if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
8511
10074
  lines.push(`${stats.totalFiles} files processed, ${stats.existingChunks} code chunks already up to date.`);
8512
10075
  } else if (stats.indexedChunks === 0) {
@@ -8551,6 +10114,19 @@ function formatIndexStats(stats, verbose = false) {
8551
10114
  }
8552
10115
  function formatStatus(status) {
8553
10116
  if (!status.indexed) {
10117
+ if (status.warning) {
10118
+ return status.warning;
10119
+ }
10120
+ if (status.failedBatchesCount > 0) {
10121
+ const lines2 = [
10122
+ "Codebase is not indexed. The last indexing run left failed embedding batches.",
10123
+ "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."
10124
+ ];
10125
+ if (status.failedBatchesPath) {
10126
+ lines2.push(`Failed batches: ${status.failedBatchesPath}`);
10127
+ }
10128
+ return lines2.join("\n");
10129
+ }
8554
10130
  return "Codebase is not indexed. Run index_codebase to create an index.";
8555
10131
  }
8556
10132
  const lines = [
@@ -8563,6 +10139,13 @@ function formatStatus(status) {
8563
10139
  lines.push(`Current branch: ${status.currentBranch}`);
8564
10140
  lines.push(`Base branch: ${status.baseBranch}`);
8565
10141
  }
10142
+ if (status.failedBatchesCount > 0) {
10143
+ lines.push("");
10144
+ lines.push(`INDEXING WARNING: ${status.failedBatchesCount} failed embedding batch${status.failedBatchesCount === 1 ? " remains" : "es remain"}.`);
10145
+ if (status.failedBatchesPath) {
10146
+ lines.push(`Failed batches: ${status.failedBatchesPath}`);
10147
+ }
10148
+ }
8566
10149
  if (status.compatibility && !status.compatibility.compatible) {
8567
10150
  lines.push("");
8568
10151
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8620,6 +10203,9 @@ function formatCodebasePeek(results) {
8620
10203
  return formatted.join("\n");
8621
10204
  }
8622
10205
  function formatHealthCheck(result) {
10206
+ if (result.resetCorruptedIndex) {
10207
+ return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
10208
+ }
8623
10209
  if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0 && result.gcOrphanSymbols === 0 && result.gcOrphanCallEdges === 0) {
8624
10210
  return "Index is healthy. No stale entries found.";
8625
10211
  }
@@ -8679,8 +10265,8 @@ ${truncateContent(r.content)}
8679
10265
  }
8680
10266
 
8681
10267
  // src/tools/index.ts
8682
- import { existsSync as existsSync6, writeFileSync as writeFileSync2, mkdirSync, statSync as statSync2 } from "fs";
8683
- import * as path8 from "path";
10268
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
10269
+ import * as path9 from "path";
8684
10270
  var z = tool.schema;
8685
10271
  var sharedIndexer = null;
8686
10272
  var sharedProjectRoot = "";
@@ -8695,7 +10281,20 @@ function refreshIndexerFromConfig() {
8695
10281
  if (!sharedProjectRoot) {
8696
10282
  throw new Error("Codebase index tools not initialized. Plugin may not be loaded correctly.");
8697
10283
  }
8698
- sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadConfig()));
10284
+ sharedIndexer = new Indexer(sharedProjectRoot, parseConfig(loadRuntimeConfig()));
10285
+ }
10286
+ function shouldForceLocalizeProjectIndex() {
10287
+ const currentConfig = parseConfig(loadRuntimeConfig());
10288
+ if (currentConfig.scope !== "project") {
10289
+ return false;
10290
+ }
10291
+ const localIndexPath = path9.join(sharedProjectRoot, ".opencode", "index");
10292
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(sharedProjectRoot);
10293
+ if (!mainRepoRoot) {
10294
+ return false;
10295
+ }
10296
+ const inheritedIndexPath = path9.join(mainRepoRoot, ".opencode", "index");
10297
+ return !existsSync7(localIndexPath) && existsSync7(inheritedIndexPath);
8699
10298
  }
8700
10299
  function getIndexer() {
8701
10300
  if (!sharedIndexer) {
@@ -8704,9 +10303,41 @@ function getIndexer() {
8704
10303
  return sharedIndexer;
8705
10304
  }
8706
10305
  function getConfigPath() {
8707
- return path8.join(sharedProjectRoot, ".opencode", "codebase-index.json");
10306
+ return resolveWritableProjectConfigPath(sharedProjectRoot);
8708
10307
  }
8709
- function loadConfig() {
10308
+ function normalizeConfigPathValue(value, baseDir) {
10309
+ const trimmed = value.trim();
10310
+ if (!trimmed) {
10311
+ return trimmed;
10312
+ }
10313
+ const absolutePath = path9.isAbsolute(trimmed) ? trimmed : path9.resolve(baseDir, trimmed);
10314
+ return path9.normalize(absolutePath);
10315
+ }
10316
+ function serializeConfigPathValue(value, baseDir) {
10317
+ const trimmed = value.trim();
10318
+ if (!trimmed) {
10319
+ return trimmed;
10320
+ }
10321
+ const normalizeRelativePath = (candidate) => candidate.replace(/\\/g, "/");
10322
+ if (!path9.isAbsolute(trimmed)) {
10323
+ return normalizeRelativePath(path9.normalize(trimmed));
10324
+ }
10325
+ const relativePath = path9.relative(baseDir, trimmed);
10326
+ if (!relativePath || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath)) {
10327
+ return normalizeRelativePath(path9.normalize(relativePath || "."));
10328
+ }
10329
+ return path9.normalize(trimmed);
10330
+ }
10331
+ function normalizeKnowledgeBasePaths(config) {
10332
+ const normalized = { ...config };
10333
+ if (Array.isArray(normalized.knowledgeBases)) {
10334
+ normalized.knowledgeBases = normalized.knowledgeBases.map((kb) => {
10335
+ return normalizeConfigPathValue(kb, sharedProjectRoot);
10336
+ });
10337
+ }
10338
+ return normalized;
10339
+ }
10340
+ function loadRuntimeConfig() {
8710
10341
  const rawConfig = loadMergedConfig(sharedProjectRoot);
8711
10342
  const config = {};
8712
10343
  if (rawConfig && typeof rawConfig === "object") {
@@ -8714,27 +10345,32 @@ function loadConfig() {
8714
10345
  config[key] = rawConfig[key];
8715
10346
  }
8716
10347
  }
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
- });
10348
+ return normalizeKnowledgeBasePaths(config);
10349
+ }
10350
+ function loadEditableConfig() {
10351
+ const rawConfig = loadProjectConfigLayer(sharedProjectRoot);
10352
+ const config = {};
10353
+ if (rawConfig && typeof rawConfig === "object") {
10354
+ for (const key of Object.keys(rawConfig)) {
10355
+ config[key] = rawConfig[key];
10356
+ }
8728
10357
  }
8729
- return config;
10358
+ return normalizeKnowledgeBasePaths(config);
8730
10359
  }
8731
10360
  function saveConfig(config) {
8732
- const configDir = path8.join(sharedProjectRoot, ".opencode");
8733
- if (!existsSync6(configDir)) {
8734
- mkdirSync(configDir, { recursive: true });
8735
- }
8736
10361
  const configPath = getConfigPath();
8737
- writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
10362
+ const configDir = path9.dirname(configPath);
10363
+ const configBaseDir = path9.dirname(configDir);
10364
+ if (!existsSync7(configDir)) {
10365
+ mkdirSync3(configDir, { recursive: true });
10366
+ }
10367
+ const serializableConfig = { ...config };
10368
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
10369
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
10370
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
10371
+ );
10372
+ }
10373
+ writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
8738
10374
  }
8739
10375
  var codebase_peek = tool({
8740
10376
  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 +10400,17 @@ var index_codebase = tool({
8764
10400
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
8765
10401
  },
8766
10402
  async execute(args, context) {
8767
- const indexer = getIndexer();
10403
+ let indexer = getIndexer();
8768
10404
  if (args.estimateOnly) {
8769
10405
  const estimate = await indexer.estimateCost();
8770
10406
  return formatCostEstimate(estimate);
8771
10407
  }
8772
10408
  if (args.force) {
10409
+ if (shouldForceLocalizeProjectIndex()) {
10410
+ materializeLocalProjectConfig(sharedProjectRoot, loadProjectConfigLayer(sharedProjectRoot));
10411
+ refreshIndexerFromConfig();
10412
+ indexer = getIndexer();
10413
+ }
8773
10414
  await indexer.clearIndex();
8774
10415
  }
8775
10416
  const stats = await indexer.index((progress) => {
@@ -8950,8 +10591,8 @@ var add_knowledge_base = tool({
8950
10591
  },
8951
10592
  async execute(args) {
8952
10593
  const inputPath = args.path.trim();
8953
- const resolvedPath = path8.isAbsolute(inputPath) ? inputPath : path8.resolve(sharedProjectRoot, inputPath);
8954
- if (!existsSync6(resolvedPath)) {
10594
+ const resolvedPath = path9.isAbsolute(inputPath) ? inputPath : path9.resolve(sharedProjectRoot, inputPath);
10595
+ if (!existsSync7(resolvedPath)) {
8955
10596
  return `Error: Directory does not exist: ${resolvedPath}`;
8956
10597
  }
8957
10598
  try {
@@ -8962,11 +10603,11 @@ var add_knowledge_base = tool({
8962
10603
  } catch (error) {
8963
10604
  return `Error: Cannot access directory: ${resolvedPath} - ${error instanceof Error ? error.message : String(error)}`;
8964
10605
  }
8965
- const config = loadConfig();
10606
+ const config = loadEditableConfig();
8966
10607
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
8967
- const normalizedPath = path8.normalize(resolvedPath);
10608
+ const normalizedPath = path9.normalize(resolvedPath);
8968
10609
  const alreadyExists = knowledgeBases.some(
8969
- (kb) => path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedPath
10610
+ (kb) => path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedPath
8970
10611
  );
8971
10612
  if (alreadyExists) {
8972
10613
  return `Knowledge base already configured: ${resolvedPath}`;
@@ -8990,7 +10631,7 @@ var list_knowledge_bases = tool({
8990
10631
  description: "List all configured knowledge base folders that are indexed alongside the main project.",
8991
10632
  args: {},
8992
10633
  async execute() {
8993
- const config = loadConfig();
10634
+ const config = loadRuntimeConfig();
8994
10635
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
8995
10636
  if (knowledgeBases.length === 0) {
8996
10637
  return "No knowledge bases configured. Use add_knowledge_base to add folders.";
@@ -9000,8 +10641,8 @@ var list_knowledge_bases = tool({
9000
10641
  `;
9001
10642
  for (let i = 0; i < knowledgeBases.length; i++) {
9002
10643
  const kb = knowledgeBases[i];
9003
- const resolvedPath = path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb);
9004
- const exists = existsSync6(resolvedPath);
10644
+ const resolvedPath = path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb);
10645
+ const exists = existsSync7(resolvedPath);
9005
10646
  result += `[${i + 1}] ${kb}
9006
10647
  `;
9007
10648
  result += ` Resolved: ${resolvedPath}
@@ -9029,14 +10670,14 @@ var remove_knowledge_base = tool({
9029
10670
  },
9030
10671
  async execute(args) {
9031
10672
  const inputPath = args.path.trim();
9032
- const config = loadConfig();
10673
+ const config = loadEditableConfig();
9033
10674
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9034
10675
  if (knowledgeBases.length === 0) {
9035
10676
  return "No knowledge bases configured.";
9036
10677
  }
9037
- const normalizedInput = path8.normalize(inputPath);
10678
+ const normalizedInput = path9.normalize(inputPath);
9038
10679
  const index = knowledgeBases.findIndex(
9039
- (kb) => path8.normalize(kb) === normalizedInput || path8.normalize(path8.isAbsolute(kb) ? kb : path8.resolve(sharedProjectRoot, kb)) === normalizedInput
10680
+ (kb) => path9.normalize(kb) === normalizedInput || path9.normalize(path9.isAbsolute(kb) ? kb : path9.resolve(sharedProjectRoot, kb)) === normalizedInput
9040
10681
  );
9041
10682
  if (index === -1) {
9042
10683
  let result2 = `Knowledge base not found: ${inputPath}
@@ -9068,8 +10709,8 @@ Run /index to rebuild the index without the removed knowledge base.`;
9068
10709
  });
9069
10710
 
9070
10711
  // src/commands/loader.ts
9071
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
9072
- import * as path9 from "path";
10712
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
10713
+ import * as path10 from "path";
9073
10714
  function parseFrontmatter(content) {
9074
10715
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
9075
10716
  const match = content.match(frontmatterRegex);
@@ -9090,15 +10731,15 @@ function parseFrontmatter(content) {
9090
10731
  }
9091
10732
  function loadCommandsFromDirectory(commandsDir) {
9092
10733
  const commands = /* @__PURE__ */ new Map();
9093
- if (!existsSync7(commandsDir)) {
10734
+ if (!existsSync8(commandsDir)) {
9094
10735
  return commands;
9095
10736
  }
9096
10737
  const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
9097
10738
  for (const file of files) {
9098
- const filePath = path9.join(commandsDir, file);
10739
+ const filePath = path10.join(commandsDir, file);
9099
10740
  const content = readFileSync6(filePath, "utf-8");
9100
10741
  const { frontmatter, body } = parseFrontmatter(content);
9101
- const name = path9.basename(file, ".md");
10742
+ const name = path10.basename(file, ".md");
9102
10743
  const description = frontmatter.description || `Run the ${name} command`;
9103
10744
  commands.set(name, {
9104
10745
  description,
@@ -9385,9 +11026,9 @@ function replaceActiveWatcher(nextWatcher) {
9385
11026
  function getCommandsDir() {
9386
11027
  let currentDir = process.cwd();
9387
11028
  if (typeof import.meta !== "undefined" && import.meta.url) {
9388
- currentDir = path10.dirname(fileURLToPath2(import.meta.url));
11029
+ currentDir = path11.dirname(fileURLToPath2(import.meta.url));
9389
11030
  }
9390
- return path10.join(currentDir, "..", "commands");
11031
+ return path11.join(currentDir, "..", "commands");
9391
11032
  }
9392
11033
  var plugin = async ({ directory }) => {
9393
11034
  try {