opencode-codebase-index 0.22.5 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -333,7 +333,7 @@ var require_ignore = __commonJS({
333
333
  // path matching.
334
334
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
335
335
  // @returns {TestResult} true if a file is ignored
336
- test(path28, checkUnignored, mode) {
336
+ test(path30, checkUnignored, mode) {
337
337
  let ignored = false;
338
338
  let unignored = false;
339
339
  let matchedRule;
@@ -342,7 +342,7 @@ var require_ignore = __commonJS({
342
342
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
343
343
  return;
344
344
  }
345
- const matched = rule[mode].test(path28);
345
+ const matched = rule[mode].test(path30);
346
346
  if (!matched) {
347
347
  return;
348
348
  }
@@ -363,17 +363,17 @@ var require_ignore = __commonJS({
363
363
  var throwError = (message, Ctor) => {
364
364
  throw new Ctor(message);
365
365
  };
366
- var checkPath = (path28, originalPath, doThrow) => {
367
- if (!isString(path28)) {
366
+ var checkPath = (path30, originalPath, doThrow) => {
367
+ if (!isString(path30)) {
368
368
  return doThrow(
369
369
  `path must be a string, but got \`${originalPath}\``,
370
370
  TypeError
371
371
  );
372
372
  }
373
- if (!path28) {
373
+ if (!path30) {
374
374
  return doThrow(`path must not be empty`, TypeError);
375
375
  }
376
- if (checkPath.isNotRelative(path28)) {
376
+ if (checkPath.isNotRelative(path30)) {
377
377
  const r = "`path.relative()`d";
378
378
  return doThrow(
379
379
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -382,7 +382,7 @@ var require_ignore = __commonJS({
382
382
  }
383
383
  return true;
384
384
  };
385
- var isNotRelative = (path28) => REGEX_TEST_INVALID_PATH.test(path28);
385
+ var isNotRelative = (path30) => REGEX_TEST_INVALID_PATH.test(path30);
386
386
  checkPath.isNotRelative = isNotRelative;
387
387
  checkPath.convert = (p) => p;
388
388
  var Ignore2 = class {
@@ -412,19 +412,19 @@ var require_ignore = __commonJS({
412
412
  }
413
413
  // @returns {TestResult}
414
414
  _test(originalPath, cache, checkUnignored, slices) {
415
- const path28 = originalPath && checkPath.convert(originalPath);
415
+ const path30 = originalPath && checkPath.convert(originalPath);
416
416
  checkPath(
417
- path28,
417
+ path30,
418
418
  originalPath,
419
419
  this._strictPathCheck ? throwError : RETURN_FALSE
420
420
  );
421
- return this._t(path28, cache, checkUnignored, slices);
421
+ return this._t(path30, cache, checkUnignored, slices);
422
422
  }
423
- checkIgnore(path28) {
424
- if (!REGEX_TEST_TRAILING_SLASH.test(path28)) {
425
- return this.test(path28);
423
+ checkIgnore(path30) {
424
+ if (!REGEX_TEST_TRAILING_SLASH.test(path30)) {
425
+ return this.test(path30);
426
426
  }
427
- const slices = path28.split(SLASH2).filter(Boolean);
427
+ const slices = path30.split(SLASH2).filter(Boolean);
428
428
  slices.pop();
429
429
  if (slices.length) {
430
430
  const parent = this._t(
@@ -437,18 +437,18 @@ var require_ignore = __commonJS({
437
437
  return parent;
438
438
  }
439
439
  }
440
- return this._rules.test(path28, false, MODE_CHECK_IGNORE);
440
+ return this._rules.test(path30, false, MODE_CHECK_IGNORE);
441
441
  }
442
- _t(path28, cache, checkUnignored, slices) {
443
- if (path28 in cache) {
444
- return cache[path28];
442
+ _t(path30, cache, checkUnignored, slices) {
443
+ if (path30 in cache) {
444
+ return cache[path30];
445
445
  }
446
446
  if (!slices) {
447
- slices = path28.split(SLASH2).filter(Boolean);
447
+ slices = path30.split(SLASH2).filter(Boolean);
448
448
  }
449
449
  slices.pop();
450
450
  if (!slices.length) {
451
- return cache[path28] = this._rules.test(path28, checkUnignored, MODE_IGNORE);
451
+ return cache[path30] = this._rules.test(path30, checkUnignored, MODE_IGNORE);
452
452
  }
453
453
  const parent = this._t(
454
454
  slices.join(SLASH2) + SLASH2,
@@ -456,29 +456,29 @@ var require_ignore = __commonJS({
456
456
  checkUnignored,
457
457
  slices
458
458
  );
459
- return cache[path28] = parent.ignored ? parent : this._rules.test(path28, checkUnignored, MODE_IGNORE);
459
+ return cache[path30] = parent.ignored ? parent : this._rules.test(path30, checkUnignored, MODE_IGNORE);
460
460
  }
461
- ignores(path28) {
462
- return this._test(path28, this._ignoreCache, false).ignored;
461
+ ignores(path30) {
462
+ return this._test(path30, this._ignoreCache, false).ignored;
463
463
  }
464
464
  createFilter() {
465
- return (path28) => !this.ignores(path28);
465
+ return (path30) => !this.ignores(path30);
466
466
  }
467
467
  filter(paths) {
468
468
  return makeArray(paths).filter(this.createFilter());
469
469
  }
470
470
  // @returns {TestResult}
471
- test(path28) {
472
- return this._test(path28, this._testCache, true);
471
+ test(path30) {
472
+ return this._test(path30, this._testCache, true);
473
473
  }
474
474
  };
475
475
  var factory = (options) => new Ignore2(options);
476
- var isPathValid = (path28) => checkPath(path28 && checkPath.convert(path28), path28, RETURN_FALSE);
476
+ var isPathValid = (path30) => checkPath(path30 && checkPath.convert(path30), path30, RETURN_FALSE);
477
477
  var setupWindows = () => {
478
478
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
479
479
  checkPath.convert = makePosix;
480
480
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
481
- checkPath.isNotRelative = (path28) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path28) || isNotRelative(path28);
481
+ checkPath.isNotRelative = (path30) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path30) || isNotRelative(path30);
482
482
  };
483
483
  if (
484
484
  // Detect `process` so that it can run in browsers.
@@ -663,7 +663,7 @@ __export(index_exports, {
663
663
  module.exports = __toCommonJS(index_exports);
664
664
 
665
665
  // src/adapters/opencode.ts
666
- var path27 = __toESM(require("path"), 1);
666
+ var path29 = __toESM(require("path"), 1);
667
667
  var import_url = require("url");
668
668
 
669
669
  // src/config/constants.ts
@@ -724,6 +724,17 @@ var EMBEDDING_MODELS = {
724
724
  maxTokens: 2048,
725
725
  costPer1MTokens: 0.15,
726
726
  taskAble: true
727
+ },
728
+ "gemini-embedding-2": {
729
+ provider: "google",
730
+ model: "gemini-embedding-2",
731
+ // Keep a conservative, testable default embedding dimension. Gemini Embedding 2 supports
732
+ // flexible dimensions via outputDimensionality.
733
+ dimensions: 1536,
734
+ maxTokens: 8192,
735
+ costPer1MTokens: 0.15,
736
+ taskAble: false,
737
+ promptStyle: "embedding-2"
727
738
  }
728
739
  },
729
740
  "openai": {
@@ -757,26 +768,15 @@ var EMBEDDING_MODELS = {
757
768
  maxTokens: 512,
758
769
  costPer1MTokens: 0
759
770
  }
760
- },
761
- "github-copilot": {
762
- "text-embedding-3-small": {
763
- provider: "github-copilot",
764
- model: "text-embedding-3-small",
765
- dimensions: 1536,
766
- maxTokens: 8191,
767
- costPer1MTokens: 0
768
- }
769
771
  }
770
772
  };
771
773
  var DEFAULT_PROVIDER_MODELS = {
772
- "github-copilot": "text-embedding-3-small",
773
774
  "openai": "text-embedding-3-small",
774
775
  "google": "gemini-embedding-001",
775
776
  "ollama": "nomic-embed-text"
776
777
  };
777
778
  var AUTO_DETECT_PROVIDER_ORDER = [
778
779
  "ollama",
779
- "github-copilot",
780
780
  "openai",
781
781
  "google"
782
782
  ];
@@ -802,6 +802,9 @@ function getDefaultIndexingConfig() {
802
802
  maxDepth: 5,
803
803
  maxFilesPerDirectory: 100,
804
804
  fallbackToTextOnMaxChunks: true,
805
+ // Must stay in sync with DEFAULT_LINES_PER_CHUNK in native/src/lib.rs (the napi
806
+ // fallback used when a native caller omits the argument).
807
+ linesPerChunk: 30,
805
808
  gitBlame: { enabled: false }
806
809
  };
807
810
  }
@@ -935,6 +938,7 @@ function parseConfig(raw) {
935
938
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
936
939
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
937
940
  fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
941
+ linesPerChunk: typeof rawIndexing.linesPerChunk === "number" && Number.isFinite(rawIndexing.linesPerChunk) ? Math.min(Math.max(1, Math.floor(rawIndexing.linesPerChunk)), 4294967295) : defaultIndexing.linesPerChunk,
938
942
  gitBlame: {
939
943
  enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
940
944
  }
@@ -977,6 +981,7 @@ function parseConfig(raw) {
977
981
  let embeddingModel;
978
982
  let customProvider;
979
983
  let reranker;
984
+ const githubCopilotDeprecationMessage = '`embeddingProvider: "github-copilot"` is deprecated and no longer available. Migrate existing configs to `embeddingProvider: "google"` and select an explicit Google model. For existing indexes, run `index_codebase` with `force: true` after changing to `gemini-embedding-001` or `gemini-embedding-2` to rebuild embeddings. See docs/configuration.md for details.';
980
985
  if (embeddingProviderValue === "custom") {
981
986
  embeddingProvider = "custom";
982
987
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -1016,6 +1021,8 @@ function parseConfig(raw) {
1016
1021
  } else if (rawEmbeddingModel) {
1017
1022
  embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
1018
1023
  }
1024
+ } else if (embeddingProviderValue === "github-copilot") {
1025
+ throw new Error(githubCopilotDeprecationMessage);
1019
1026
  } else {
1020
1027
  embeddingProvider = "auto";
1021
1028
  }
@@ -1046,10 +1053,21 @@ function parseConfig(raw) {
1046
1053
  timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
1047
1054
  };
1048
1055
  }
1056
+ const rawEmbedding = input.embedding && typeof input.embedding === "object" ? input.embedding : {};
1057
+ const rawEmbeddingBatch = rawEmbedding.batch && typeof rawEmbedding.batch === "object" ? rawEmbedding.batch : null;
1058
+ const embeddingMaxBatchItems = typeof rawEmbeddingBatch?.maxBatchItems === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchItems) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchItems)) : void 0;
1059
+ const embeddingMaxBatchTokens = typeof rawEmbeddingBatch?.maxBatchTokens === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchTokens) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchTokens)) : void 0;
1060
+ const embedding = embeddingMaxBatchItems !== void 0 || embeddingMaxBatchTokens !== void 0 ? {
1061
+ batch: {
1062
+ ...embeddingMaxBatchItems !== void 0 ? { maxBatchItems: embeddingMaxBatchItems } : {},
1063
+ ...embeddingMaxBatchTokens !== void 0 ? { maxBatchTokens: embeddingMaxBatchTokens } : {}
1064
+ }
1065
+ } : {};
1049
1066
  return {
1050
1067
  embeddingProvider,
1051
1068
  embeddingModel,
1052
1069
  customProvider,
1070
+ embedding,
1053
1071
  scope: isValidScope(scopeValue) ? scopeValue : "project",
1054
1072
  include: includeValue ?? DEFAULT_INCLUDE,
1055
1073
  exclude: excludeValue ?? DEFAULT_EXCLUDE,
@@ -1141,11 +1159,11 @@ function resolveGitDir(repoRoot) {
1141
1159
  return null;
1142
1160
  }
1143
1161
  try {
1144
- const stat4 = (0, import_fs2.statSync)(gitPath);
1145
- if (stat4.isDirectory()) {
1162
+ const stat5 = (0, import_fs2.statSync)(gitPath);
1163
+ if (stat5.isDirectory()) {
1146
1164
  return gitPath;
1147
1165
  }
1148
- if (stat4.isFile()) {
1166
+ if (stat5.isFile()) {
1149
1167
  const content = (0, import_fs2.readFileSync)(gitPath, "utf-8").trim();
1150
1168
  const match = content.match(/^gitdir:\s*(.+)$/);
1151
1169
  if (match) {
@@ -2214,7 +2232,7 @@ function analyzeQueryIntent(query) {
2214
2232
  }
2215
2233
  function isTestPath(filePath) {
2216
2234
  const normalized = normalizePath(filePath);
2217
- return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /\.(?:test|spec)\.[^/]+$/u.test(normalized);
2235
+ return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /(?:\.(?:test|spec)|_(?:test|spec))\.[^/]+$/u.test(normalized) || /(?:^|\/)(?:test|spec)_[^/]+\.[^/]+$/u.test(normalized);
2218
2236
  }
2219
2237
  function isFixturePath(filePath) {
2220
2238
  const normalized = normalizePath(filePath);
@@ -2316,6 +2334,10 @@ function scoreCandidate(query, intent, candidate, originalIndex) {
2316
2334
  let boost = 0;
2317
2335
  if (intent.primary === "conceptual") {
2318
2336
  boost += Math.min(0.14, overlap * 0.14);
2337
+ if (intent.preferSourcePaths) {
2338
+ boost += implementationPath ? 0.32 : 0;
2339
+ if (testPath || fixturePath || docsPath) boost -= 0.35;
2340
+ }
2319
2341
  if (generatedOrVendor) boost -= 0.18;
2320
2342
  if (importChunk || weakContainer) boost -= 0.04;
2321
2343
  } else if (intent.primary === "test") {
@@ -2589,8 +2611,8 @@ function formatExactSearchHandoff(results) {
2589
2611
  }
2590
2612
  function formatContextEvidence(result, index) {
2591
2613
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
2592
- const path28 = compactEvidenceValue(result.filePath, 120);
2593
- return `[${index}] ${result.chunkType}${symbol} in ${path28}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2614
+ const path30 = compactEvidenceValue(result.filePath, 120);
2615
+ return `[${index}] ${result.chunkType}${symbol} in ${path30}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2594
2616
  }
2595
2617
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
2596
2618
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -3283,6 +3305,19 @@ function parseOwner(value) {
3283
3305
  if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3284
3306
  if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
3285
3307
  if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
3308
+ if (candidate.recoveryProtocolVersion !== void 0 && candidate.recoveryProtocolVersion !== 1) return null;
3309
+ if (candidate.projectRoot !== void 0 && typeof candidate.projectRoot !== "string") return null;
3310
+ if (candidate.scopedRoots !== void 0) {
3311
+ if (!Array.isArray(candidate.scopedRoots) || candidate.scopedRoots.some((root) => typeof root !== "string")) {
3312
+ return null;
3313
+ }
3314
+ }
3315
+ if (candidate.clearRecovery !== void 0) {
3316
+ const recovery = candidate.clearRecovery;
3317
+ if (typeof recovery !== "object" || recovery === null || recovery.phase !== "clearing" || typeof recovery.embeddingProvider !== "string" || recovery.embeddingProvider.length === 0 || typeof recovery.embeddingModel !== "string" || recovery.embeddingModel.length === 0 || !Number.isInteger(recovery.embeddingDimensions) || (recovery.embeddingDimensions ?? 0) <= 0 || typeof recovery.embeddingStrategyVersion !== "string" || recovery.embeddingStrategyVersion.length === 0 || recovery.compatibilityDecision !== "compatible" && recovery.compatibilityDecision !== "embedding-strategy-mismatch" && recovery.compatibilityDecision !== "incompatible" || candidate.operation !== "clear" && candidate.operation !== "force-index") {
3318
+ return null;
3319
+ }
3320
+ }
3286
3321
  return candidate;
3287
3322
  }
3288
3323
  function parseReclaimOwner(value) {
@@ -3523,13 +3558,18 @@ function isTransientIndexLockContention(error) {
3523
3558
  if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
3524
3559
  return error.reason === "active" || error.reason === "reclaiming";
3525
3560
  }
3526
- function acquireIndexLock(indexPath, operation) {
3561
+ function acquireIndexLock(indexPath, operation, recoveryScope) {
3527
3562
  (0, import_fs5.mkdirSync)(indexPath, { recursive: true });
3528
3563
  const canonicalIndexPath = import_fs5.realpathSync.native(indexPath);
3529
3564
  const lockPath = path9.join(canonicalIndexPath, "indexing.lock");
3530
3565
  cleanupDeadPublicationCandidates(canonicalIndexPath);
3531
3566
  for (let attempt = 0; attempt < 6; attempt += 1) {
3532
- const owner = createOwner(operation);
3567
+ const owner = recoveryScope === void 0 ? createOwner(operation) : {
3568
+ ...createOwner(operation),
3569
+ recoveryProtocolVersion: 1,
3570
+ projectRoot: recoveryScope.projectRoot,
3571
+ scopedRoots: recoveryScope.scopedRoots
3572
+ };
3533
3573
  if (publishJsonDirectory(lockPath, owner)) {
3534
3574
  const lease = {
3535
3575
  canonicalIndexPath,
@@ -3594,6 +3634,33 @@ function releaseIndexLock(lease) {
3594
3634
  }
3595
3635
  return true;
3596
3636
  }
3637
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
3638
+ const currentOwner = readDirectoryOwner(lease.lockPath);
3639
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
3640
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
3641
+ }
3642
+ const nextOwner = { ...currentOwner };
3643
+ if (clearRecovery === null) {
3644
+ delete nextOwner.clearRecovery;
3645
+ } else {
3646
+ nextOwner.clearRecovery = clearRecovery;
3647
+ }
3648
+ const ownerPath = path9.join(lease.lockPath, OWNER_FILE_NAME);
3649
+ const temporaryPath = path9.join(
3650
+ lease.lockPath,
3651
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${(0, import_crypto.randomUUID)()}`
3652
+ );
3653
+ try {
3654
+ (0, import_fs5.writeFileSync)(temporaryPath, JSON.stringify(nextOwner), {
3655
+ encoding: "utf-8",
3656
+ flag: "wx",
3657
+ mode: 384
3658
+ });
3659
+ retryTransientFilesystemOperation(() => (0, import_fs5.renameSync)(temporaryPath, ownerPath));
3660
+ } finally {
3661
+ if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { force: true });
3662
+ }
3663
+ }
3597
3664
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
3598
3665
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
3599
3666
  temporaryCounter += 1;
@@ -3743,8 +3810,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3743
3810
  if (entry.isDirectory()) {
3744
3811
  subdirs.push({ fullPath, relativePath });
3745
3812
  } else if (entry.isFile()) {
3746
- const stat4 = await import_fs6.promises.stat(fullPath);
3747
- if (stat4.size > maxFileSize) {
3813
+ const stat5 = await import_fs6.promises.stat(fullPath);
3814
+ if (stat5.size > maxFileSize) {
3748
3815
  skipped.push({ path: relativePath, reason: "too_large" });
3749
3816
  continue;
3750
3817
  }
@@ -3762,7 +3829,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3762
3829
  }
3763
3830
  }
3764
3831
  if (matched) {
3765
- filesInDir.push({ path: fullPath, size: stat4.size });
3832
+ filesInDir.push({ path: fullPath, size: stat5.size });
3766
3833
  }
3767
3834
  }
3768
3835
  }
@@ -3819,8 +3886,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3819
3886
  }
3820
3887
  for (const resolvedKbRoot of normalizedRoots) {
3821
3888
  try {
3822
- const stat4 = await import_fs6.promises.stat(resolvedKbRoot);
3823
- if (!stat4.isDirectory()) {
3889
+ const stat5 = await import_fs6.promises.stat(resolvedKbRoot);
3890
+ if (!stat5.isDirectory()) {
3824
3891
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3825
3892
  continue;
3826
3893
  }
@@ -3854,7 +3921,7 @@ function getErrorMessage(error) {
3854
3921
  return error instanceof Error ? error.message : String(error);
3855
3922
  }
3856
3923
  function runCommand(file, args, options) {
3857
- return new Promise((resolve15, reject) => {
3924
+ return new Promise((resolve17, reject) => {
3858
3925
  childProcess.execFile(
3859
3926
  file,
3860
3927
  args,
@@ -3864,7 +3931,7 @@ function runCommand(file, args, options) {
3864
3931
  reject(error);
3865
3932
  return;
3866
3933
  }
3867
- resolve15(stdout);
3934
+ resolve17(stdout);
3868
3935
  }
3869
3936
  );
3870
3937
  });
@@ -4009,10 +4076,10 @@ function safeFailureMessage(error) {
4009
4076
  }
4010
4077
  function cancellableDelay(delayMs, signal) {
4011
4078
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
4012
- return new Promise((resolve15, reject) => {
4079
+ return new Promise((resolve17, reject) => {
4013
4080
  const timer = setTimeout(() => {
4014
4081
  signal.removeEventListener("abort", onAbort);
4015
- resolve15();
4082
+ resolve17();
4016
4083
  }, delayMs);
4017
4084
  timer.unref?.();
4018
4085
  const onAbort = () => {
@@ -4024,15 +4091,15 @@ function cancellableDelay(delayMs, signal) {
4024
4091
  }
4025
4092
  function withTimeout(promise, timeoutMs) {
4026
4093
  if (timeoutMs <= 0) return Promise.resolve(void 0);
4027
- return new Promise((resolve15) => {
4028
- const timer = setTimeout(() => resolve15(void 0), timeoutMs);
4094
+ return new Promise((resolve17) => {
4095
+ const timer = setTimeout(() => resolve17(void 0), timeoutMs);
4029
4096
  timer.unref?.();
4030
4097
  void promise.then((value) => {
4031
4098
  clearTimeout(timer);
4032
- resolve15(value);
4099
+ resolve17(value);
4033
4100
  }, () => {
4034
4101
  clearTimeout(timer);
4035
- resolve15(void 0);
4102
+ resolve17(void 0);
4036
4103
  });
4037
4104
  });
4038
4105
  }
@@ -4414,17 +4481,17 @@ var AutoIndexCoordinator = class {
4414
4481
  }
4415
4482
  }
4416
4483
  waitForBatteryRetry(delayMs) {
4417
- return new Promise((resolve15) => {
4484
+ return new Promise((resolve17) => {
4418
4485
  const timer = setTimeout(() => {
4419
4486
  if (this.batteryRetryTimer === timer) {
4420
4487
  this.batteryRetryTimer = null;
4421
4488
  this.resolveBatteryRetry = null;
4422
4489
  }
4423
- resolve15();
4490
+ resolve17();
4424
4491
  }, delayMs);
4425
4492
  timer.unref?.();
4426
4493
  this.batteryRetryTimer = timer;
4427
- this.resolveBatteryRetry = resolve15;
4494
+ this.resolveBatteryRetry = resolve17;
4428
4495
  });
4429
4496
  }
4430
4497
  cancelBatteryRetry() {
@@ -4432,9 +4499,9 @@ var AutoIndexCoordinator = class {
4432
4499
  clearTimeout(this.batteryRetryTimer);
4433
4500
  this.batteryRetryTimer = null;
4434
4501
  }
4435
- const resolve15 = this.resolveBatteryRetry;
4502
+ const resolve17 = this.resolveBatteryRetry;
4436
4503
  this.resolveBatteryRetry = null;
4437
- resolve15?.();
4504
+ resolve17?.();
4438
4505
  }
4439
4506
  finishBatteryCheck(batteryCheck) {
4440
4507
  if (this.batteryCheck !== batteryCheck) return;
@@ -4639,7 +4706,7 @@ function pTimeout(promise, options) {
4639
4706
  } = options;
4640
4707
  let timer;
4641
4708
  let abortHandler;
4642
- const wrappedPromise = new Promise((resolve15, reject) => {
4709
+ const wrappedPromise = new Promise((resolve17, reject) => {
4643
4710
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
4644
4711
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
4645
4712
  }
@@ -4653,7 +4720,7 @@ function pTimeout(promise, options) {
4653
4720
  };
4654
4721
  signal.addEventListener("abort", abortHandler, { once: true });
4655
4722
  }
4656
- promise.then(resolve15, reject);
4723
+ promise.then(resolve17, reject);
4657
4724
  if (milliseconds === Number.POSITIVE_INFINITY) {
4658
4725
  return;
4659
4726
  }
@@ -4661,7 +4728,7 @@ function pTimeout(promise, options) {
4661
4728
  timer = customTimers.setTimeout.call(void 0, () => {
4662
4729
  if (fallback) {
4663
4730
  try {
4664
- resolve15(fallback());
4731
+ resolve17(fallback());
4665
4732
  } catch (error) {
4666
4733
  reject(error);
4667
4734
  }
@@ -4671,7 +4738,7 @@ function pTimeout(promise, options) {
4671
4738
  promise.cancel();
4672
4739
  }
4673
4740
  if (message === false) {
4674
- resolve15();
4741
+ resolve17();
4675
4742
  } else if (message instanceof Error) {
4676
4743
  reject(message);
4677
4744
  } else {
@@ -5073,7 +5140,7 @@ var PQueue = class extends import_index.default {
5073
5140
  // Assign unique ID if not provided
5074
5141
  id: options.id ?? (this.#idAssigner++).toString()
5075
5142
  };
5076
- return new Promise((resolve15, reject) => {
5143
+ return new Promise((resolve17, reject) => {
5077
5144
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
5078
5145
  let cleanupQueueAbortHandler = () => void 0;
5079
5146
  const run = async () => {
@@ -5113,7 +5180,7 @@ var PQueue = class extends import_index.default {
5113
5180
  })]);
5114
5181
  }
5115
5182
  const result = await operation;
5116
- resolve15(result);
5183
+ resolve17(result);
5117
5184
  this.emit("completed", result);
5118
5185
  } catch (error) {
5119
5186
  reject(error);
@@ -5301,13 +5368,13 @@ var PQueue = class extends import_index.default {
5301
5368
  });
5302
5369
  }
5303
5370
  async #onEvent(event, filter) {
5304
- return new Promise((resolve15) => {
5371
+ return new Promise((resolve17) => {
5305
5372
  const listener = () => {
5306
5373
  if (filter && !filter()) {
5307
5374
  return;
5308
5375
  }
5309
5376
  this.off(event, listener);
5310
- resolve15();
5377
+ resolve17();
5311
5378
  };
5312
5379
  this.on(event, listener);
5313
5380
  });
@@ -5593,7 +5660,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5593
5660
  const finalDelay = Math.min(delayTime, remainingTime);
5594
5661
  options.signal?.throwIfAborted();
5595
5662
  if (finalDelay > 0) {
5596
- await new Promise((resolve15, reject) => {
5663
+ await new Promise((resolve17, reject) => {
5597
5664
  const onAbort = () => {
5598
5665
  clearTimeout(timeoutToken);
5599
5666
  options.signal?.removeEventListener("abort", onAbort);
@@ -5601,7 +5668,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5601
5668
  };
5602
5669
  const timeoutToken = setTimeout(() => {
5603
5670
  options.signal?.removeEventListener("abort", onAbort);
5604
- resolve15();
5671
+ resolve17();
5605
5672
  }, finalDelay);
5606
5673
  if (options.unref) {
5607
5674
  timeoutToken.unref?.();
@@ -5737,8 +5804,6 @@ async function tryDetectProvider() {
5737
5804
  }
5738
5805
  async function getProviderCredentials(provider) {
5739
5806
  switch (provider) {
5740
- case "github-copilot":
5741
- return getGitHubCopilotCredentials();
5742
5807
  case "openai":
5743
5808
  return getOpenAICredentials();
5744
5809
  case "google":
@@ -5749,22 +5814,6 @@ async function getProviderCredentials(provider) {
5749
5814
  return null;
5750
5815
  }
5751
5816
  }
5752
- function getGitHubCopilotCredentials() {
5753
- const authData = loadOpenCodeAuth();
5754
- const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
5755
- if (!copilotAuth || copilotAuth.type !== "oauth") {
5756
- return null;
5757
- }
5758
- const auth = copilotAuth;
5759
- const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
5760
- return {
5761
- provider: "github-copilot",
5762
- baseUrl,
5763
- refreshToken: copilotAuth.refresh,
5764
- accessToken: copilotAuth.access,
5765
- tokenExpires: copilotAuth.expires
5766
- };
5767
- }
5768
5817
  function getOpenAICredentials() {
5769
5818
  const authData = loadOpenCodeAuth();
5770
5819
  const openaiAuth = authData["openai"];
@@ -5890,8 +5939,6 @@ async function tryDetectOllamaProvider() {
5890
5939
  }
5891
5940
  function getProviderDisplayName(provider) {
5892
5941
  switch (provider) {
5893
- case "github-copilot":
5894
- return "GitHub Copilot";
5895
5942
  case "openai":
5896
5943
  return "OpenAI";
5897
5944
  case "google":
@@ -6116,44 +6163,6 @@ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
6116
6163
  }
6117
6164
  };
6118
6165
 
6119
- // src/embeddings/providers/github-copilot.ts
6120
- var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
6121
- constructor(credentials, modelInfo) {
6122
- super(credentials, modelInfo);
6123
- }
6124
- getToken() {
6125
- if (!this.credentials.refreshToken) {
6126
- throw new Error("No OAuth token available for GitHub");
6127
- }
6128
- return this.credentials.refreshToken;
6129
- }
6130
- async embedBatch(texts) {
6131
- const token = this.getToken();
6132
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
6133
- method: "POST",
6134
- headers: {
6135
- Authorization: `Bearer ${token}`,
6136
- "Content-Type": "application/json",
6137
- Accept: "application/vnd.github+json",
6138
- "X-GitHub-Api-Version": "2022-11-28"
6139
- },
6140
- body: JSON.stringify({
6141
- model: `openai/${this.modelInfo.model}`,
6142
- input: texts
6143
- })
6144
- });
6145
- if (!response.ok) {
6146
- const error = (await response.text()).slice(0, 500);
6147
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
6148
- }
6149
- const data = await response.json();
6150
- return {
6151
- embeddings: data.data.map((d) => d.embedding),
6152
- totalTokensUsed: data.usage.total_tokens
6153
- };
6154
- }
6155
- };
6156
-
6157
6166
  // src/embeddings/providers/google.ts
6158
6167
  var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
6159
6168
  static BATCH_SIZE = 20;
@@ -6161,24 +6170,30 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6161
6170
  super(credentials, modelInfo);
6162
6171
  }
6163
6172
  async embedQuery(query) {
6164
- const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6165
- const result = await this.embedWithTaskType([query], taskType);
6173
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6174
+ const texts = [
6175
+ this.modelInfo.model === "gemini-embedding-2" ? `task: code retrieval | query: ${query}` : query
6176
+ ];
6177
+ const result = await this.embedWithTaskType(texts, taskType);
6166
6178
  return {
6167
6179
  embedding: result.embeddings[0],
6168
6180
  tokensUsed: result.totalTokensUsed
6169
6181
  };
6170
6182
  }
6171
6183
  async embedDocument(document) {
6172
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6173
- const result = await this.embedWithTaskType([document], taskType);
6184
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6185
+ const result = await this.embedWithTaskType([
6186
+ this.modelInfo.model === "gemini-embedding-2" ? `title: none | text: ${document}` : document
6187
+ ], taskType);
6174
6188
  return {
6175
6189
  embedding: result.embeddings[0],
6176
6190
  tokensUsed: result.totalTokensUsed
6177
6191
  };
6178
6192
  }
6179
6193
  async embedBatch(texts) {
6180
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6181
- return this.embedWithTaskType(texts, taskType);
6194
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6195
+ const formattedTexts = this.modelInfo.model === "gemini-embedding-2" ? texts.map((text) => `title: none | text: ${text}`) : texts;
6196
+ return this.embedWithTaskType(formattedTexts, taskType);
6182
6197
  }
6183
6198
  async embedWithTaskType(texts, taskType) {
6184
6199
  const batches = [];
@@ -6228,6 +6243,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6228
6243
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
6229
6244
  static MIN_TRUNCATION_CHARS = 512;
6230
6245
  static REQUEST_TIMEOUT_MS = 12e4;
6246
+ // Set when /api/embed returns 404 so subsequent multi-text batches skip the
6247
+ // batched endpoint and go straight to the legacy per-text path (one probe per
6248
+ // old ollama install, not one probe per batch).
6249
+ batchEndpointUnavailable = false;
6231
6250
  constructor(credentials, modelInfo) {
6232
6251
  super(credentials, modelInfo);
6233
6252
  }
@@ -6245,6 +6264,21 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6245
6264
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
6246
6265
  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");
6247
6266
  }
6267
+ // True for a 404 from the newer /api/embed endpoint, i.e. an ollama version that
6268
+ // does not provide it. embedBatch uses this to fall back to the legacy per-text
6269
+ // /api/embeddings path so old ollama installs do not regress.
6270
+ isBatchEndpointUnavailableError(error) {
6271
+ const message = error instanceof Error ? error.message : String(error);
6272
+ return message.includes("Ollama /api/embed not available");
6273
+ }
6274
+ // True for a malformed /api/embed response (wrong vector count or a bad vector).
6275
+ // embedBatch falls back to the per-text path on this so a bad batch response
6276
+ // re-embeds each text cleanly. A text that then fails per-text is not isolated
6277
+ // here; it is isolated on the recovery run, which re-embeds one text per request.
6278
+ isBatchValidationError(error) {
6279
+ const message = error instanceof Error ? error.message : String(error);
6280
+ return message.includes("invalid embedding batch");
6281
+ }
6248
6282
  buildTruncationCandidates(text) {
6249
6283
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
6250
6284
  const candidateLimits = /* @__PURE__ */ new Set();
@@ -6346,7 +6380,74 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6346
6380
  tokensUsed: this.estimateTokens(text)
6347
6381
  };
6348
6382
  }
6349
- async embedBatch(texts) {
6383
+ // Embeds many texts in one POST /api/embed request (input: string[]). Ollama
6384
+ // encodes each input independently, so the model context length applies per input
6385
+ // (the upstream splitter already bounds each input), not over the batch. This
6386
+ // amortizes N HTTP round-trips into one.
6387
+ async embedMany(texts) {
6388
+ const controller = new AbortController();
6389
+ const timeout = setTimeout(
6390
+ () => controller.abort(),
6391
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
6392
+ );
6393
+ let response;
6394
+ try {
6395
+ response = await fetch(`${this.credentials.baseUrl}/api/embed`, {
6396
+ method: "POST",
6397
+ headers: {
6398
+ "Content-Type": "application/json"
6399
+ },
6400
+ body: JSON.stringify({
6401
+ model: this.modelInfo.model,
6402
+ input: texts,
6403
+ truncate: false
6404
+ }),
6405
+ signal: controller.signal
6406
+ });
6407
+ } catch (error) {
6408
+ if (error instanceof Error && error.name === "AbortError") {
6409
+ throw new Error(
6410
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
6411
+ );
6412
+ }
6413
+ throw error;
6414
+ } finally {
6415
+ clearTimeout(timeout);
6416
+ }
6417
+ if (!response.ok) {
6418
+ const error = (await response.text()).slice(0, 500);
6419
+ if (response.status === 404) {
6420
+ throw new Error(`Ollama /api/embed not available: ${response.status} - ${error}`);
6421
+ }
6422
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
6423
+ }
6424
+ let parsed;
6425
+ try {
6426
+ parsed = await response.json();
6427
+ } catch {
6428
+ throw new Error(
6429
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6430
+ );
6431
+ }
6432
+ const data = parsed && typeof parsed === "object" ? parsed : {};
6433
+ if (!Array.isArray(data.embeddings) || data.embeddings.length !== texts.length || data.embeddings.some(
6434
+ (value) => !Array.isArray(value) || value.length !== this.modelInfo.dimensions || value.some((v) => typeof v !== "number" || !Number.isFinite(v))
6435
+ )) {
6436
+ throw new Error(
6437
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6438
+ );
6439
+ }
6440
+ return {
6441
+ embeddings: data.embeddings,
6442
+ totalTokensUsed: texts.reduce((sum, text) => sum + this.estimateTokens(text), 0)
6443
+ };
6444
+ }
6445
+ // Per-text /api/embeddings path shared by the single-text fast path and the
6446
+ // batch fallback. Uses the legacy endpoint one text at a time, so each text gets
6447
+ // its own truncation safety net and a vector validated on its own. A text that
6448
+ // hard-fails per-text throws here and fails the whole request batch; the recovery
6449
+ // run re-embeds one text per request to isolate it.
6450
+ async embedOneByOne(texts) {
6350
6451
  const results = [];
6351
6452
  for (const text of texts) {
6352
6453
  results.push(await this.embedSingleWithFallback(text));
@@ -6356,6 +6457,26 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6356
6457
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
6357
6458
  };
6358
6459
  }
6460
+ async embedBatch(texts) {
6461
+ if (texts.length === 0) {
6462
+ return { embeddings: [], totalTokensUsed: 0 };
6463
+ }
6464
+ if (texts.length === 1 || this.batchEndpointUnavailable) {
6465
+ return this.embedOneByOne(texts);
6466
+ }
6467
+ try {
6468
+ return await this.embedMany(texts);
6469
+ } catch (error) {
6470
+ if (this.isBatchEndpointUnavailableError(error)) {
6471
+ this.batchEndpointUnavailable = true;
6472
+ return this.embedOneByOne(texts);
6473
+ }
6474
+ if (!this.isContextLengthError(error) && !this.isBatchValidationError(error)) {
6475
+ throw error;
6476
+ }
6477
+ return this.embedOneByOne(texts);
6478
+ }
6479
+ }
6359
6480
  };
6360
6481
 
6361
6482
  // src/embeddings/providers/openai.ts
@@ -6390,8 +6511,6 @@ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
6390
6511
  // src/embeddings/provider.ts
6391
6512
  function createEmbeddingProvider(configuredProviderInfo) {
6392
6513
  switch (configuredProviderInfo.provider) {
6393
- case "github-copilot":
6394
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6395
6514
  case "openai":
6396
6515
  return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6397
6516
  case "google":
@@ -6407,85 +6526,6 @@ function createEmbeddingProvider(configuredProviderInfo) {
6407
6526
  }
6408
6527
  }
6409
6528
 
6410
- // src/rerank/index.ts
6411
- function createReranker(config) {
6412
- if (!config.enabled) {
6413
- return new NoOpReranker();
6414
- }
6415
- return new SiliconFlowReranker(config);
6416
- }
6417
- var NoOpReranker = class {
6418
- isAvailable() {
6419
- return false;
6420
- }
6421
- async rerank(_query, documents, _topN) {
6422
- return {
6423
- results: documents.map((_, index) => ({ index, relevanceScore: 0 }))
6424
- };
6425
- }
6426
- };
6427
- var SiliconFlowReranker = class {
6428
- config;
6429
- constructor(config) {
6430
- this.config = config;
6431
- }
6432
- isAvailable() {
6433
- return this.config.enabled && !!this.config.baseUrl && !!this.config.model;
6434
- }
6435
- async rerank(query, documents, topN) {
6436
- if (documents.length === 0) {
6437
- return { results: [] };
6438
- }
6439
- const headers = {
6440
- "Content-Type": "application/json"
6441
- };
6442
- if (this.config.apiKey) {
6443
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
6444
- }
6445
- const baseUrl = this.config.baseUrl;
6446
- if (!baseUrl) {
6447
- throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
6448
- }
6449
- const timeoutMs = this.config.timeoutMs ?? 3e4;
6450
- const controller = new AbortController();
6451
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
6452
- try {
6453
- const response = await fetch(`${baseUrl}/rerank`, {
6454
- method: "POST",
6455
- headers,
6456
- body: JSON.stringify({
6457
- model: this.config.model,
6458
- query,
6459
- documents,
6460
- top_n: topN ?? this.config.topN ?? 20,
6461
- return_documents: false
6462
- }),
6463
- signal: controller.signal
6464
- });
6465
- clearTimeout(timeout);
6466
- if (!response.ok) {
6467
- const errorText = await response.text();
6468
- throw new Error(`Rerank API error: ${response.status} - ${errorText}`);
6469
- }
6470
- const data = await response.json();
6471
- return {
6472
- results: data.results.map((r) => ({
6473
- index: r.index,
6474
- relevanceScore: r.relevance_score,
6475
- document: r.document?.text
6476
- })),
6477
- tokensUsed: data.meta?.tokens?.input_tokens
6478
- };
6479
- } catch (error) {
6480
- clearTimeout(timeout);
6481
- if (error instanceof Error && error.name === "AbortError") {
6482
- throw new Error(`Rerank API request timed out after ${timeoutMs}ms`);
6483
- }
6484
- throw error;
6485
- }
6486
- }
6487
- };
6488
-
6489
6529
  // src/utils/cost.ts
6490
6530
  function estimateChunksFromFiles(files) {
6491
6531
  let totalChunks = 0;
@@ -7222,12 +7262,12 @@ try {
7222
7262
  }
7223
7263
 
7224
7264
  // src/native/parsing.ts
7225
- function parseFileAsText(filePath, content) {
7226
- const result = native.parseFileAsText(filePath, content);
7265
+ function parseFileAsText(filePath, content, linesPerChunk) {
7266
+ const result = native.parseFileAsText(filePath, content, linesPerChunk);
7227
7267
  return result.map(mapChunk);
7228
7268
  }
7229
- function parseFiles(files) {
7230
- const result = native.parseFiles(files);
7269
+ function parseFiles(files, linesPerChunk) {
7270
+ const result = native.parseFiles(files, linesPerChunk);
7231
7271
  return result.map((f) => ({
7232
7272
  path: f.path,
7233
7273
  chunks: f.chunks.map(mapChunk),
@@ -7304,13 +7344,13 @@ var VectorStore = class {
7304
7344
  const metadata = items.map((i) => JSON.stringify(i.metadata));
7305
7345
  this.inner.addBatch(ids, vectors, metadata);
7306
7346
  }
7307
- search(queryVector, limit = 10) {
7347
+ search(queryVector, limit = 10, allowedIds) {
7308
7348
  if (queryVector.length !== this.dimensions) {
7309
7349
  throw new Error(
7310
7350
  `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
7311
7351
  );
7312
7352
  }
7313
- const results = this.inner.search(queryVector, limit);
7353
+ const results = allowedIds === void 0 ? this.inner.search(queryVector, limit) : this.inner.searchFiltered(queryVector, limit, allowedIds);
7314
7354
  return results.map((r) => ({
7315
7355
  id: r.id,
7316
7356
  score: r.score,
@@ -7538,6 +7578,10 @@ var Database = class _Database {
7538
7578
  this.throwIfClosed();
7539
7579
  return this.inner.getBranchChunkIds(branch);
7540
7580
  }
7581
+ getChunkIdsByBlameDate(since, until) {
7582
+ this.throwIfClosed();
7583
+ return this.inner.getChunkIdsByBlameDate(since, until);
7584
+ }
7541
7585
  getBranchDelta(branch, baseBranch) {
7542
7586
  this.throwIfClosed();
7543
7587
  return this.inner.getBranchDelta(branch, baseBranch);
@@ -8046,8 +8090,8 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
8046
8090
  return false;
8047
8091
  }
8048
8092
  function isPathWithinRoot(filePath, rootPath) {
8049
- const relative12 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8050
- return relative12 === "" || !relative12.startsWith(`..${path15.sep}`) && relative12 !== ".." && !path15.isAbsolute(relative12);
8093
+ const relative14 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8094
+ return relative14 === "" || !relative14.startsWith(`..${path15.sep}`) && relative14 !== ".." && !path15.isAbsolute(relative14);
8051
8095
  }
8052
8096
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
8053
8097
  if (await pathExists(worktreePath)) return false;
@@ -8424,11 +8468,11 @@ function normalizeFiles(rawFiles, projectRoot) {
8424
8468
  for (const raw of rawFiles) {
8425
8469
  if (raw.length === 0) continue;
8426
8470
  const absolute = path16.resolve(root, raw);
8427
- const relative12 = path16.relative(root, absolute);
8428
- if (path16.isAbsolute(raw) || relative12 === ".." || relative12.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative12)) {
8471
+ const relative14 = path16.relative(root, absolute);
8472
+ if (path16.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative14)) {
8429
8473
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
8430
8474
  }
8431
- const cleaned = relative12.startsWith(`.${path16.sep}`) ? relative12.slice(2) : relative12;
8475
+ const cleaned = relative14.startsWith(`.${path16.sep}`) ? relative14.slice(2) : relative14;
8432
8476
  if (!seen.has(cleaned)) {
8433
8477
  seen.add(cleaned);
8434
8478
  result.push(cleaned);
@@ -8660,7 +8704,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
8660
8704
  return cached;
8661
8705
  }
8662
8706
  }
8663
- const overfetchLimit = Math.max(options.limit * 4, options.limit);
8707
+ const overfetchFactor = prioritizeSourcePaths ? 12 : 4;
8708
+ const overfetchLimit = Math.max(options.limit * overfetchFactor, options.limit);
8664
8709
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
8665
8710
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
8666
8711
  const rerankPool = fused.slice(0, rerankPoolLimit);
@@ -9425,6 +9470,18 @@ function createFailedBatchWriter(targetPath) {
9425
9470
  temporaryPath
9426
9471
  };
9427
9472
  }
9473
+ function writeFailedBatchRecords(targetPath, records) {
9474
+ const writer = createFailedBatchWriter(targetPath);
9475
+ try {
9476
+ for (const record of records) {
9477
+ writer.write(record);
9478
+ }
9479
+ writer.commit();
9480
+ } catch (error) {
9481
+ writer.cleanup();
9482
+ throw error;
9483
+ }
9484
+ }
9428
9485
  function* readLegacyFailedBatchRecords(filePath, options) {
9429
9486
  const rawData = fs2.readFileSync(filePath, "utf-8");
9430
9487
  const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
@@ -9707,14 +9764,18 @@ function getSafeEmbeddingChunkTokenLimit(provider) {
9707
9764
  const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
9708
9765
  return Math.min(2e3, maxChunkTokens);
9709
9766
  }
9710
- function getDynamicBatchOptions(provider) {
9711
- if (provider.provider === "ollama") {
9712
- return {
9713
- maxBatchTokens: provider.modelInfo.maxTokens,
9714
- maxBatchItems: 1
9715
- };
9767
+ var DEFAULT_OLLAMA_MAX_BATCH_ITEMS = 16;
9768
+ var DEFAULT_OLLAMA_MAX_BATCH_TOKENS = 65536;
9769
+ function getDynamicBatchOptions(provider, embeddingBatch) {
9770
+ if (provider.provider !== "ollama") {
9771
+ return {};
9716
9772
  }
9717
- return {};
9773
+ const base = { maxBatchTokens: DEFAULT_OLLAMA_MAX_BATCH_TOKENS, maxBatchItems: DEFAULT_OLLAMA_MAX_BATCH_ITEMS };
9774
+ return {
9775
+ ...base,
9776
+ ...typeof embeddingBatch?.maxBatchTokens === "number" && Number.isFinite(embeddingBatch.maxBatchTokens) ? { maxBatchTokens: embeddingBatch.maxBatchTokens } : {},
9777
+ ...typeof embeddingBatch?.maxBatchItems === "number" && Number.isFinite(embeddingBatch.maxBatchItems) ? { maxBatchItems: embeddingBatch.maxBatchItems } : {}
9778
+ };
9718
9779
  }
9719
9780
  function isSqliteCorruptionError(error) {
9720
9781
  const message = getErrorMessage4(error).toLowerCase();
@@ -9732,6 +9793,14 @@ function getPendingChunkId(rawChunk) {
9732
9793
  const id = rawChunk.id;
9733
9794
  return typeof id === "string" ? id : null;
9734
9795
  }
9796
+ function parseBlameTimestamp(value, endOfDay) {
9797
+ let timestampMs = Date.parse(value);
9798
+ if (Number.isNaN(timestampMs)) return null;
9799
+ if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
9800
+ timestampMs += 24 * 60 * 60 * 1e3 - 1;
9801
+ }
9802
+ return Math.floor(timestampMs / 1e3);
9803
+ }
9735
9804
  function metadataFromBlame(blame) {
9736
9805
  if (!blame) {
9737
9806
  return {};
@@ -9878,7 +9947,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
9878
9947
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
9879
9948
  return [...promoted, ...remainder];
9880
9949
  }
9881
- function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9950
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source", allowNonSourcePaths = false) {
9882
9951
  if (!prioritizeSourcePaths) {
9883
9952
  return [];
9884
9953
  }
@@ -9898,7 +9967,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9898
9967
  if (!isImplementationChunkType(chunkType)) {
9899
9968
  return false;
9900
9969
  }
9901
- if (!isLikelyImplementationPath2(chunk.filePath)) {
9970
+ if (!allowNonSourcePaths && !isLikelyImplementationPath2(chunk.filePath)) {
9902
9971
  return false;
9903
9972
  }
9904
9973
  const nameLower = (chunk.name ?? "").toLowerCase();
@@ -9962,7 +10031,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9962
10031
  }
9963
10032
  foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
9964
10033
  }
9965
- if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
10034
+ if (foundCoveringChunk || !allowNonSourcePaths && !isLikelyImplementationPath2(symbol.filePath)) {
9966
10035
  continue;
9967
10036
  }
9968
10037
  const symbolName = symbol.name.toLowerCase();
@@ -10016,7 +10085,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
10016
10085
  const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
10017
10086
  if (ranked.length === 0) {
10018
10087
  const implementationFallback = fallbackCandidates.filter(
10019
- (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath2(candidate.metadata.filePath)
10088
+ (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && (allowNonSourcePaths || isLikelyImplementationPath2(candidate.metadata.filePath))
10020
10089
  );
10021
10090
  for (const candidate of implementationFallback) {
10022
10091
  const nameLower = (candidate.metadata.name ?? "").toLowerCase();
@@ -10132,10 +10201,16 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
10132
10201
  return false;
10133
10202
  }
10134
10203
  if (options?.blameSince) {
10135
- const sinceMs = Date.parse(options.blameSince);
10136
- if (Number.isNaN(sinceMs)) return false;
10204
+ const since = parseBlameTimestamp(options.blameSince, false);
10205
+ if (since === null) return false;
10206
+ const committedAt = candidate.metadata.blameCommittedAt;
10207
+ if (committedAt === void 0 || committedAt < since) return false;
10208
+ }
10209
+ if (options?.blameUntil) {
10210
+ const until = parseBlameTimestamp(options.blameUntil, true);
10211
+ if (until === null) return false;
10137
10212
  const committedAt = candidate.metadata.blameCommittedAt;
10138
- if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
10213
+ if (committedAt === void 0 || committedAt > until) return false;
10139
10214
  }
10140
10215
  return true;
10141
10216
  }
@@ -10171,7 +10246,6 @@ var Indexer = class _Indexer {
10171
10246
  database = null;
10172
10247
  provider = null;
10173
10248
  configuredProviderInfo = null;
10174
- reranker = null;
10175
10249
  fileHashCache = /* @__PURE__ */ new Map();
10176
10250
  fileHashCachePath = "";
10177
10251
  failedBatchesPath = "";
@@ -10192,9 +10266,10 @@ var Indexer = class _Indexer {
10192
10266
  writerArtifactFingerprint = null;
10193
10267
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
10194
10268
  fileBatchLimits;
10269
+ checkpointIntervalChunks;
10195
10270
  constructor(projectRoot, config, host, runtimeOptions = {}) {
10196
10271
  this.projectRoot = projectRoot;
10197
- this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10272
+ this.projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10198
10273
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
10199
10274
  this.branchNameOverride = runtimeOptions.branchName;
10200
10275
  this.catalogIdentityOverride = runtimeOptions.catalogIdentity;
@@ -10204,6 +10279,7 @@ var Indexer = class _Indexer {
10204
10279
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
10205
10280
  this.indexPathOverride = runtimeOptions.indexPath;
10206
10281
  this.fileBatchLimits = runtimeOptions.fileBatchLimits;
10282
+ this.checkpointIntervalChunks = runtimeOptions.checkpointIntervalChunks;
10207
10283
  this.config = config;
10208
10284
  this.host = host;
10209
10285
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -10315,6 +10391,9 @@ var Indexer = class _Indexer {
10315
10391
  return path19.resolve(targetPath);
10316
10392
  }
10317
10393
  }
10394
+ getProjectIdentityHash(projectRoot) {
10395
+ return hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10396
+ }
10318
10397
  isProjectOwnedIndexPath() {
10319
10398
  return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
10320
10399
  }
@@ -10331,7 +10410,6 @@ var Indexer = class _Indexer {
10331
10410
  this.database = null;
10332
10411
  this.provider = null;
10333
10412
  this.configuredProviderInfo = null;
10334
- this.reranker = null;
10335
10413
  this.indexCompatibility = null;
10336
10414
  this.initializationMode = "none";
10337
10415
  this.readIssues = [];
@@ -10352,7 +10430,10 @@ var Indexer = class _Indexer {
10352
10430
  }
10353
10431
  async withIndexMutationLease(operation, callback) {
10354
10432
  this.refreshBranchInfo();
10355
- const lease = acquireIndexLock(this.indexPath, operation);
10433
+ const lease = acquireIndexLock(this.indexPath, operation, {
10434
+ projectRoot: this.projectRoot,
10435
+ scopedRoots: this.getScopedRoots()
10436
+ });
10356
10437
  this.indexPath = lease.canonicalIndexPath;
10357
10438
  this.refreshRuntimeArtifactPaths();
10358
10439
  this.activeIndexLease = lease;
@@ -10407,6 +10488,7 @@ var Indexer = class _Indexer {
10407
10488
  }
10408
10489
  loadFileHashCache() {
10409
10490
  if (!(0, import_fs12.existsSync)(this.fileHashCachePath)) {
10491
+ this.fileHashCache = /* @__PURE__ */ new Map();
10410
10492
  return;
10411
10493
  }
10412
10494
  try {
@@ -10446,10 +10528,10 @@ var Indexer = class _Indexer {
10446
10528
  invertedIndex.serialize()
10447
10529
  );
10448
10530
  }
10449
- getScopedRoots() {
10450
- const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
10531
+ getScopedRoots(projectRoot = this.projectRoot) {
10532
+ const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
10451
10533
  for (const kbRoot of this.config.knowledgeBases) {
10452
- roots.add(this.getCanonicalPath(path19.resolve(this.projectRoot, kbRoot)));
10534
+ roots.add(this.getCanonicalPath(path19.resolve(projectRoot, kbRoot)));
10453
10535
  }
10454
10536
  return Array.from(roots);
10455
10537
  }
@@ -10520,14 +10602,17 @@ var Indexer = class _Indexer {
10520
10602
  getLegacyBranchCatalogKey() {
10521
10603
  return this.currentBranch || "default";
10522
10604
  }
10523
- getLegacyMigrationMetadataKey() {
10524
- return `index.globalBranchMigration.${this.projectIdentityHash}`;
10605
+ getLegacyMigrationMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10606
+ return `index.globalBranchMigration.${projectIdentityHash}`;
10525
10607
  }
10526
- getProjectEmbeddingStrategyMetadataKey() {
10527
- return `index.embeddingStrategyVersion.${this.projectIdentityHash}`;
10608
+ getProjectEmbeddingStrategyMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10609
+ return `index.embeddingStrategyVersion.${projectIdentityHash}`;
10528
10610
  }
10529
- getProjectForceReembedMetadataKey() {
10530
- return `index.forceReembed.${this.projectIdentityHash}`;
10611
+ getProjectForceReembedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10612
+ return `index.forceReembed.${projectIdentityHash}`;
10613
+ }
10614
+ getProjectMigrationFinalizedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10615
+ return `index.migrationFinalized.${projectIdentityHash}`;
10531
10616
  }
10532
10617
  getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
10533
10618
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
@@ -10633,7 +10718,7 @@ var Indexer = class _Indexer {
10633
10718
  const legacy = this.getLegacyBranchCatalogKey();
10634
10719
  return primary === legacy ? [primary] : [primary, legacy];
10635
10720
  }
10636
- getProjectLocalScopedOwnershipIds(roots) {
10721
+ getProjectLocalScopedOwnershipIds(roots, projectRoot = this.projectRoot) {
10637
10722
  const chunkIds = /* @__PURE__ */ new Set();
10638
10723
  const symbolIds = /* @__PURE__ */ new Set();
10639
10724
  if (!this.database) {
@@ -10641,10 +10726,10 @@ var Indexer = class _Indexer {
10641
10726
  }
10642
10727
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
10643
10728
  ...Array.from(this.fileHashCache.keys()).filter(
10644
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10729
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10645
10730
  ),
10646
10731
  ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
10647
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10732
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10648
10733
  )
10649
10734
  ]);
10650
10735
  for (const filePath of projectLocalFilePaths) {
@@ -10657,15 +10742,16 @@ var Indexer = class _Indexer {
10657
10742
  }
10658
10743
  return { chunkIds, symbolIds };
10659
10744
  }
10660
- getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
10745
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds, projectRoot = this.projectRoot) {
10661
10746
  if (this.config.scope !== "global") {
10662
10747
  return this.getBranchCatalogCleanupKeys();
10663
10748
  }
10664
10749
  const keys = /* @__PURE__ */ new Set();
10665
10750
  const projectChunkIdSet = new Set(projectChunkIds);
10666
10751
  const projectSymbolIdSet = new Set(projectSymbolIds);
10752
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10667
10753
  for (const branchKey of this.database?.getAllBranches() ?? []) {
10668
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10754
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10669
10755
  keys.add(branchKey);
10670
10756
  continue;
10671
10757
  }
@@ -10675,8 +10761,10 @@ var Indexer = class _Indexer {
10675
10761
  keys.add(branchKey);
10676
10762
  }
10677
10763
  }
10678
- for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10679
- keys.add(branchKey);
10764
+ if (projectRoot === this.projectRoot) {
10765
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10766
+ keys.add(branchKey);
10767
+ }
10680
10768
  }
10681
10769
  return Array.from(keys);
10682
10770
  }
@@ -10684,10 +10772,10 @@ var Indexer = class _Indexer {
10684
10772
  const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
10685
10773
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
10686
10774
  }
10687
- isFileInProjectRoot(filePath) {
10775
+ isFileInProjectRoot(filePath, projectRoot = this.projectRoot) {
10688
10776
  return isPathWithinRoot2(
10689
10777
  this.getCanonicalStoredFilePath(filePath),
10690
- this.getCanonicalPath(this.projectRoot)
10778
+ this.getCanonicalPath(projectRoot)
10691
10779
  );
10692
10780
  }
10693
10781
  clearScopedFileHashCache(roots) {
@@ -10729,12 +10817,12 @@ var Indexer = class _Indexer {
10729
10817
  }
10730
10818
  return false;
10731
10819
  }
10732
- hasForeignScopedBranchData() {
10820
+ hasForeignScopedBranchData(projectRoot = this.projectRoot, roots = this.getScopedRoots(projectRoot)) {
10733
10821
  if (!this.database || this.config.scope !== "global") {
10734
10822
  return false;
10735
10823
  }
10736
- const roots = this.getScopedRoots();
10737
- const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
10824
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10825
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots, projectRoot);
10738
10826
  return this.database.getAllBranches().some(
10739
10827
  (branchKey) => {
10740
10828
  const branchChunkIds = this.database.getBranchChunkIds(branchKey);
@@ -10743,7 +10831,7 @@ var Indexer = class _Indexer {
10743
10831
  if (!hasBranchData) {
10744
10832
  return false;
10745
10833
  }
10746
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10834
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10747
10835
  return false;
10748
10836
  }
10749
10837
  const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
@@ -10752,7 +10840,7 @@ var Indexer = class _Indexer {
10752
10840
  }
10753
10841
  );
10754
10842
  }
10755
- clearSharedIndexProjectData(store, invertedIndex, database, roots) {
10843
+ clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot = this.projectRoot) {
10756
10844
  const allMetadata = store.getAllMetadata();
10757
10845
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
10758
10846
  const filePaths = /* @__PURE__ */ new Set([
@@ -10760,7 +10848,7 @@ var Indexer = class _Indexer {
10760
10848
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
10761
10849
  ]);
10762
10850
  const projectLocalFilePaths = new Set(
10763
- Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath))
10851
+ Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath, projectRoot))
10764
10852
  );
10765
10853
  const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
10766
10854
  for (const filePath of filePaths) {
@@ -10770,7 +10858,7 @@ var Indexer = class _Indexer {
10770
10858
  }
10771
10859
  const removedChunkIdList = Array.from(removedChunkIds);
10772
10860
  const projectLocalChunkIds = new Set(
10773
- scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath)).map(({ key }) => key)
10861
+ scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath, projectRoot)).map(({ key }) => key)
10774
10862
  );
10775
10863
  for (const filePath of projectLocalFilePaths) {
10776
10864
  for (const chunk of database.getChunksByFile(filePath)) {
@@ -10789,7 +10877,8 @@ var Indexer = class _Indexer {
10789
10877
  }
10790
10878
  const branchCleanupKeys = this.getProjectScopedBranchCatalogCleanupKeys(
10791
10879
  Array.from(projectLocalChunkIds),
10792
- Array.from(projectLocalSymbolIds)
10880
+ Array.from(projectLocalSymbolIds),
10881
+ projectRoot
10793
10882
  );
10794
10883
  for (const branchKey of branchCleanupKeys) {
10795
10884
  database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
@@ -10824,29 +10913,96 @@ var Indexer = class _Indexer {
10824
10913
  database.gcOrphanSymbols();
10825
10914
  database.gcOrphanEmbeddings();
10826
10915
  database.gcOrphanChunks();
10827
- store.save();
10828
10916
  this.saveInvertedIndex(invertedIndex);
10917
+ store.save();
10829
10918
  return {
10830
10919
  removedChunkIds: removedChunkIdList,
10831
10920
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
10832
10921
  };
10833
10922
  }
10923
+ getCurrentClearRecoveryState() {
10924
+ if (!this.configuredProviderInfo) {
10925
+ throw new Error("Cannot persist clear recovery state before the embedding provider is initialized");
10926
+ }
10927
+ const compatibility = this.checkCompatibility();
10928
+ const compatibilityDecision = compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible";
10929
+ return {
10930
+ phase: "clearing",
10931
+ embeddingProvider: this.configuredProviderInfo.provider,
10932
+ embeddingModel: this.configuredProviderInfo.modelInfo.model,
10933
+ embeddingDimensions: this.configuredProviderInfo.modelInfo.dimensions,
10934
+ embeddingStrategyVersion: EMBEDDING_STRATEGY_VERSION,
10935
+ compatibilityDecision
10936
+ };
10937
+ }
10938
+ beginClearRecoveryState() {
10939
+ const recovery = this.getCurrentClearRecoveryState();
10940
+ setIndexLockClearRecoveryState(this.requireActiveLease(), recovery);
10941
+ return recovery;
10942
+ }
10943
+ finishClearRecoveryState() {
10944
+ setIndexLockClearRecoveryState(this.requireActiveLease(), null);
10945
+ }
10946
+ matchesCurrentClearRecoveryConfiguration(recovery) {
10947
+ const configuredProviderInfo = this.configuredProviderInfo;
10948
+ return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
10949
+ }
10950
+ hasUnknownLegacyForceIndexClear(owner) {
10951
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && (0, import_fs12.existsSync)(path19.join(this.indexPath, "force-index-phase"));
10952
+ }
10834
10953
  async recoverFromInterruptedIndexingUnlocked(owners) {
10835
10954
  for (const owner of owners) {
10836
10955
  this.logger.warn("Detected interrupted indexing session, recovering...", {
10837
10956
  pid: owner.pid,
10838
10957
  hostname: owner.hostname,
10839
10958
  operation: owner.operation,
10840
- startedAt: owner.startedAt
10959
+ startedAt: owner.startedAt,
10960
+ projectRoot: owner.projectRoot
10841
10961
  });
10842
10962
  }
10843
10963
  if (this.config.scope === "global") {
10844
- if ((0, import_fs12.existsSync)(this.fileHashCachePath)) {
10845
- (0, import_fs12.unlinkSync)(this.fileHashCachePath);
10964
+ const clearScopes = [];
10965
+ for (const owner of owners) {
10966
+ if (this.hasUnknownLegacyForceIndexClear(owner)) {
10967
+ throw new Error(
10968
+ `Cannot automatically recover interrupted force-index ${owner.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
10969
+ );
10970
+ }
10971
+ if (owner.operation === "clear" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1) {
10972
+ throw new Error(
10973
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating recovery state is unknown. The recovery marker was retained for manual inspection.`
10974
+ );
10975
+ }
10976
+ if (owner.clearRecovery === void 0) continue;
10977
+ if (!owner.projectRoot || !owner.scopedRoots || owner.scopedRoots.length === 0) {
10978
+ throw new Error(
10979
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating project scope is unknown. The recovery marker was retained for manual inspection.`
10980
+ );
10981
+ }
10982
+ if (!this.matchesCurrentClearRecoveryConfiguration(owner.clearRecovery)) {
10983
+ throw new Error(
10984
+ `Cannot automatically recover interrupted global clear ${owner.token}: the current embedding configuration does not match the originating lease. The recovery marker was retained; retry from the originating project with matching settings.`
10985
+ );
10986
+ }
10987
+ clearScopes.push({
10988
+ projectRoot: owner.projectRoot,
10989
+ scopedRoots: owner.scopedRoots,
10990
+ compatibilityDecision: owner.clearRecovery.compatibilityDecision
10991
+ });
10992
+ }
10993
+ if (clearScopes.length > 0) {
10994
+ this.loadFileHashCache();
10995
+ }
10996
+ for (const { projectRoot, scopedRoots, compatibilityDecision } of clearScopes) {
10997
+ this.clearGlobalIndexUnlocked(projectRoot, scopedRoots, compatibilityDecision);
10846
10998
  }
10847
10999
  await this.healthCheckUnlocked();
11000
+ this.logger.info(
11001
+ clearScopes.length > 0 ? "Recovery complete, next index will rebuild all files" : "Recovery complete, next index will resume from the last checkpoint"
11002
+ );
11003
+ return;
10848
11004
  }
10849
- this.logger.info("Recovery complete, next index will re-process all files");
11005
+ this.logger.info("Recovery complete, next index will resume from the last checkpoint");
10850
11006
  }
10851
11007
  *loadSerializedFailedBatches() {
10852
11008
  let warned = false;
@@ -10884,40 +11040,126 @@ var Indexer = class _Indexer {
10884
11040
  state.writer.write(record);
10885
11041
  state.recordsWritten += record.chunks.length;
10886
11042
  }
10887
- finalizeFailedBatchWriteState(state) {
11043
+ finalizeFailedBatchWriteState(state, resolvedChunkIds = /* @__PURE__ */ new Set()) {
10888
11044
  if (state.recordsWritten > 0) {
10889
- state.writer.commit();
10890
- return;
10891
- }
10892
- state.writer.cleanup();
10893
- this.clearFailedBatchState();
10894
- }
10895
- clearFailedBatchState() {
10896
- if ((0, import_fs12.existsSync)(this.failedBatchesPath)) {
10897
- try {
10898
- (0, import_fs12.unlinkSync)(this.failedBatchesPath);
10899
- } catch {
10900
- }
10901
- }
10902
- }
10903
- rewriteFailedBatchState(shouldRetain) {
10904
- const state = this.createFailedBatchWriteState();
10905
- try {
10906
- for (const batch of this.loadSerializedFailedBatches()) {
10907
- const retainedChunks = batch.chunks.filter(shouldRetain);
10908
- if (retainedChunks.length > 0) {
10909
- this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
11045
+ const seenChunkIds = /* @__PURE__ */ new Set();
11046
+ const retained = [];
11047
+ const records = Array.from(readFailedBatchRecords(state.writer.temporaryPath));
11048
+ for (let i = records.length - 1; i >= 0; i--) {
11049
+ const chunks = records[i].chunks.filter((rawChunk) => {
11050
+ const chunkId = getPendingChunkId(rawChunk);
11051
+ if (chunkId !== null) {
11052
+ if (resolvedChunkIds.has(chunkId)) return false;
11053
+ if (seenChunkIds.has(chunkId)) return false;
11054
+ seenChunkIds.add(chunkId);
11055
+ }
11056
+ return true;
11057
+ });
11058
+ if (chunks.length > 0) {
11059
+ retained.unshift({ ...records[i], chunks });
10910
11060
  }
10911
11061
  }
10912
- this.finalizeFailedBatchWriteState(state);
10913
- } catch (error) {
10914
11062
  state.writer.cleanup();
10915
- throw error;
11063
+ if (retained.length > 0) {
11064
+ writeFailedBatchRecords(this.failedBatchesPath, retained);
11065
+ } else {
11066
+ writeFailedBatchRecords(this.failedBatchesPath, []);
11067
+ this.clearFailedBatchState();
11068
+ }
11069
+ return;
11070
+ }
11071
+ state.writer.commit();
11072
+ this.clearFailedBatchState();
11073
+ }
11074
+ getCheckpointIntervalChunks(totalChunks) {
11075
+ return Math.max(
11076
+ this.checkpointIntervalChunks ?? 2e3,
11077
+ Math.floor(totalChunks / 10)
11078
+ );
11079
+ }
11080
+ checkpointIndexRun(database, store, invertedIndex, failedProcessing, resolvedRetryChunkIds, currentFileHashes, committedFilePaths, scopedRoots, configuredProviderInfo) {
11081
+ if (!this.hasProjectForceReembedPending()) {
11082
+ this.saveIndexMetadata(configuredProviderInfo);
11083
+ this.indexCompatibility = { compatible: true };
11084
+ }
11085
+ database.commitWriteTransaction();
11086
+ database.beginWriteTransaction();
11087
+ this.saveInvertedIndex(invertedIndex);
11088
+ store.save();
11089
+ if (failedProcessing.state.recordsWritten > 0 || failedProcessing.latestById.size > 0 || failedProcessing.discardedExistingRecords) {
11090
+ for (const metadata of failedProcessing.latestById.values()) {
11091
+ const alreadyMaterialized = metadata.chunks.some((rawChunk) => {
11092
+ const chunkId = getPendingChunkId(rawChunk);
11093
+ return chunkId !== null && failedProcessing.materializedRetryIds.has(chunkId);
11094
+ });
11095
+ if (alreadyMaterialized) continue;
11096
+ this.writeFailedBatchRecord(failedProcessing.state, {
11097
+ chunks: metadata.chunks,
11098
+ attemptCount: metadata.attemptCount,
11099
+ error: metadata.error,
11100
+ lastAttempt: metadata.lastAttempt
11101
+ });
11102
+ for (const rawChunk of metadata.chunks) {
11103
+ const chunkId = getPendingChunkId(rawChunk);
11104
+ if (chunkId !== null) {
11105
+ failedProcessing.materializedRetryIds.add(chunkId);
11106
+ }
11107
+ }
11108
+ }
11109
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11110
+ failedProcessing.state = this.createFailedBatchWriteState();
11111
+ failedProcessing.discardedExistingRecords = false;
11112
+ for (const record of this.loadSerializedFailedBatches()) {
11113
+ for (const rawChunk of record.chunks) {
11114
+ const chunkId = getPendingChunkId(rawChunk);
11115
+ this.writeFailedBatchRecord(failedProcessing.state, { ...record, chunks: [rawChunk] });
11116
+ if (chunkId !== null) {
11117
+ failedProcessing.materializedRetryIds.add(chunkId);
11118
+ }
11119
+ }
11120
+ }
11121
+ }
11122
+ const partialHashes = /* @__PURE__ */ new Map();
11123
+ for (const filePath of committedFilePaths) {
11124
+ const hash = currentFileHashes.get(filePath);
11125
+ if (hash !== void 0) {
11126
+ partialHashes.set(filePath, hash);
11127
+ }
11128
+ }
11129
+ if (scopedRoots) {
11130
+ this.replaceScopedFileHashCache(partialHashes, scopedRoots);
11131
+ } else {
11132
+ this.fileHashCache = partialHashes;
11133
+ this.saveFileHashCache();
11134
+ }
11135
+ }
11136
+ clearFailedBatchState() {
11137
+ if ((0, import_fs12.existsSync)(this.failedBatchesPath)) {
11138
+ try {
11139
+ (0, import_fs12.unlinkSync)(this.failedBatchesPath);
11140
+ } catch {
11141
+ }
11142
+ }
11143
+ }
11144
+ rewriteFailedBatchState(shouldRetain) {
11145
+ const state = this.createFailedBatchWriteState();
11146
+ try {
11147
+ for (const batch of this.loadSerializedFailedBatches()) {
11148
+ const retainedChunks = batch.chunks.filter(shouldRetain);
11149
+ if (retainedChunks.length > 0) {
11150
+ this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
11151
+ }
11152
+ }
11153
+ this.finalizeFailedBatchWriteState(state);
11154
+ } catch (error) {
11155
+ state.writer.cleanup();
11156
+ throw error;
10916
11157
  }
10917
11158
  }
10918
11159
  prepareFailedBatchProcessing(roots, shouldProcess) {
10919
11160
  const state = this.createFailedBatchWriteState();
10920
11161
  const latestById = /* @__PURE__ */ new Map();
11162
+ let discardedExistingRecords = false;
10921
11163
  try {
10922
11164
  for (const batch of this.loadSerializedFailedBatches()) {
10923
11165
  for (const rawChunk of batch.chunks) {
@@ -10928,10 +11170,12 @@ var Indexer = class _Indexer {
10928
11170
  continue;
10929
11171
  }
10930
11172
  if (!shouldProcess(filePath)) {
11173
+ discardedExistingRecords = true;
10931
11174
  continue;
10932
11175
  }
10933
11176
  const chunkId = getPendingChunkId(rawChunk);
10934
11177
  if (!chunkId) {
11178
+ discardedExistingRecords = true;
10935
11179
  continue;
10936
11180
  }
10937
11181
  const existing = latestById.get(chunkId);
@@ -10939,12 +11183,18 @@ var Indexer = class _Indexer {
10939
11183
  latestById.set(chunkId, {
10940
11184
  attemptCount: batch.attemptCount,
10941
11185
  error: batch.error,
10942
- lastAttempt: batch.lastAttempt
11186
+ lastAttempt: batch.lastAttempt,
11187
+ chunks: [rawChunk]
10943
11188
  });
10944
11189
  }
10945
11190
  }
10946
11191
  }
10947
- return { state, latestById };
11192
+ return {
11193
+ state,
11194
+ latestById,
11195
+ materializedRetryIds: /* @__PURE__ */ new Set(),
11196
+ discardedExistingRecords
11197
+ };
10948
11198
  } catch (error) {
10949
11199
  state.writer.cleanup();
10950
11200
  throw error;
@@ -10980,10 +11230,34 @@ var Indexer = class _Indexer {
10980
11230
  }
10981
11231
  }
10982
11232
  }
11233
+ restoreMissingChunkRows(database, chunks) {
11234
+ const missing = [];
11235
+ for (const chunk of chunks) {
11236
+ if (database.getChunk(chunk.id)) {
11237
+ continue;
11238
+ }
11239
+ missing.push({
11240
+ chunkId: chunk.id,
11241
+ contentHash: chunk.contentHash,
11242
+ filePath: chunk.metadata.filePath,
11243
+ startLine: chunk.metadata.startLine,
11244
+ endLine: chunk.metadata.endLine,
11245
+ nodeType: chunk.metadata.chunkType,
11246
+ name: chunk.metadata.name,
11247
+ language: chunk.metadata.language,
11248
+ blameSha: chunk.metadata.blameSha,
11249
+ blameAuthor: chunk.metadata.blameAuthor,
11250
+ blameAuthorEmail: chunk.metadata.blameAuthorEmail,
11251
+ blameCommittedAt: chunk.metadata.blameCommittedAt,
11252
+ blameSummary: chunk.metadata.blameSummary
11253
+ });
11254
+ }
11255
+ if (missing.length > 0) {
11256
+ database.upsertChunksBatch(missing);
11257
+ }
11258
+ }
10983
11259
  getProviderRateLimits(provider) {
10984
11260
  switch (provider) {
10985
- case "github-copilot":
10986
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
10987
11261
  case "openai":
10988
11262
  return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
10989
11263
  case "google":
@@ -11052,16 +11326,17 @@ var Indexer = class _Indexer {
11052
11326
  const embeddingPartsByChunk = /* @__PURE__ */ new Map();
11053
11327
  const completedVectorsByChunkId = /* @__PURE__ */ new Map();
11054
11328
  const completedChunkIds = /* @__PURE__ */ new Set();
11055
- const requestBatches = createPendingEmbeddingRequestBatches(
11056
- chunksNeedingEmbedding,
11057
- getDynamicBatchOptions(options.configuredProviderInfo)
11058
- );
11329
+ const batchOptions = getDynamicBatchOptions(options.configuredProviderInfo, this.config.embedding?.batch);
11330
+ if (options.forceSingleItemBatches && options.configuredProviderInfo.provider === "ollama") {
11331
+ batchOptions.maxBatchItems = 1;
11332
+ }
11333
+ const requestBatches = createPendingEmbeddingRequestBatches(chunksNeedingEmbedding, batchOptions);
11059
11334
  let fatalError;
11060
11335
  for (const requestBatch of requestBatches) {
11061
11336
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
11062
11337
  const task = options.queue.add(async () => {
11063
11338
  if (options.rateLimitState.backoffMs > 0) {
11064
- await new Promise((resolve15) => setTimeout(resolve15, options.rateLimitState.backoffMs));
11339
+ await new Promise((resolve17) => setTimeout(resolve17, options.rateLimitState.backoffMs));
11065
11340
  }
11066
11341
  try {
11067
11342
  const embeddingResult = await pRetry(
@@ -11618,7 +11893,7 @@ var Indexer = class _Indexer {
11618
11893
  }
11619
11894
  if (!this.configuredProviderInfo) {
11620
11895
  throw new Error(
11621
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11896
+ "No embedding provider available. Configure OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11622
11897
  );
11623
11898
  }
11624
11899
  this.logger.info("Initializing indexer", {
@@ -11628,15 +11903,6 @@ var Indexer = class _Indexer {
11628
11903
  rerankerEnabled: this.config.reranker?.enabled ?? false
11629
11904
  });
11630
11905
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
11631
- if (this.config.reranker?.enabled) {
11632
- this.reranker = createReranker(this.config.reranker);
11633
- if (this.reranker.isAvailable()) {
11634
- this.logger.info("Reranker initialized", {
11635
- model: this.config.reranker.model,
11636
- baseUrl: this.config.reranker.baseUrl
11637
- });
11638
- }
11639
- }
11640
11906
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
11641
11907
  const storePath = path19.join(this.indexPath, "vectors");
11642
11908
  const vectorMetadataPath = `${storePath}.meta.json`;
@@ -11658,7 +11924,20 @@ var Indexer = class _Indexer {
11658
11924
  ]);
11659
11925
  }
11660
11926
  if (recoveredOwners.length > 0 && this.config.scope === "project") {
11661
- await this.resetLocalIndexArtifacts();
11927
+ const unknownLegacyForceIndex = recoveredOwners.find(
11928
+ (owner) => this.hasUnknownLegacyForceIndexClear(owner)
11929
+ );
11930
+ if (unknownLegacyForceIndex) {
11931
+ throw new Error(
11932
+ `Cannot automatically recover interrupted force-index ${unknownLegacyForceIndex.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
11933
+ );
11934
+ }
11935
+ const shouldReset = recoveredOwners.some(
11936
+ (owner) => owner.clearRecovery !== void 0 || owner.operation === "clear" && owner.recoveryProtocolVersion !== 1
11937
+ );
11938
+ if (shouldReset) {
11939
+ await this.resetLocalIndexArtifacts();
11940
+ }
11662
11941
  }
11663
11942
  this.store = new VectorStore(storePath, dimensions);
11664
11943
  if ((0, import_fs12.existsSync)(storePath) || (0, import_fs12.existsSync)(vectorMetadataPath)) {
@@ -12294,7 +12573,17 @@ var Indexer = class _Indexer {
12294
12573
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
12295
12574
  for (const file of files) {
12296
12575
  const storedPath = this.toStoredFilePath(file.path);
12297
- const currentHash = hashFile(file.path);
12576
+ let currentHash;
12577
+ try {
12578
+ currentHash = hashFile(file.path);
12579
+ } catch (error) {
12580
+ stats.skippedFiles.push({ path: this.toCanonicalFilePath(file.path), reason: "unreadable" });
12581
+ this.logger.warn("Skipped unreadable file during indexing", {
12582
+ path: file.path,
12583
+ error: getErrorMessage4(error)
12584
+ });
12585
+ continue;
12586
+ }
12298
12587
  currentFileHashes.set(storedPath, currentHash);
12299
12588
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
12300
12589
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
@@ -12302,7 +12591,8 @@ var Indexer = class _Indexer {
12302
12591
  );
12303
12592
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12304
12593
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
12305
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12594
+ const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
12595
+ if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12306
12596
  unchangedFilePaths.add(storedPath);
12307
12597
  this.logger.recordCacheHit();
12308
12598
  } else {
@@ -12428,6 +12718,9 @@ var Indexer = class _Indexer {
12428
12718
  }
12429
12719
  }
12430
12720
  let processedChangedFiles = 0;
12721
+ let lastCheckpointChunks = 0;
12722
+ const committedFilePaths = new Set(unchangedFilePaths);
12723
+ const resolvedRetryChunkIds = /* @__PURE__ */ new Set();
12431
12724
  for (const descriptorBatch of iterateOrderedFileBatches(
12432
12725
  changedFileDescriptors,
12433
12726
  (descriptor) => descriptor.sourceBytes,
@@ -12441,7 +12734,7 @@ var Indexer = class _Indexer {
12441
12734
  const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
12442
12735
  const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
12443
12736
  const parseStartTime = import_perf_hooks.performance.now();
12444
- const parsedFiles = parseFiles(loadedFiles);
12737
+ const parsedFiles = parseFiles(loadedFiles, this.config.indexing.linesPerChunk);
12445
12738
  const parseMs = import_perf_hooks.performance.now() - parseStartTime;
12446
12739
  this.logger.recordFilesParsed(parsedFiles.length);
12447
12740
  this.logger.recordParseDuration(parseMs);
@@ -12464,7 +12757,7 @@ var Indexer = class _Indexer {
12464
12757
  }
12465
12758
  let chunksToProcess = parsed.chunks;
12466
12759
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12467
- chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
12760
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content, this.config.indexing.linesPerChunk);
12468
12761
  }
12469
12762
  chunksToProcess = selectIndexableChunks(
12470
12763
  chunksToProcess,
@@ -12598,6 +12891,10 @@ var Indexer = class _Indexer {
12598
12891
  }
12599
12892
  if (symbolBatch.length > 0) {
12600
12893
  database.upsertSymbolsBatch(symbolBatch);
12894
+ database.addSymbolsToBranchBatch(
12895
+ this.getBranchCatalogKey(),
12896
+ symbolBatch.map((symbol) => symbol.id)
12897
+ );
12601
12898
  }
12602
12899
  if (edgeBatch.length > 0) {
12603
12900
  database.upsertCallEdgesBatch(edgeBatch);
@@ -12633,6 +12930,12 @@ var Indexer = class _Indexer {
12633
12930
  forceReembed: forceScopedReembed,
12634
12931
  reuseCachedEmbeddings: true,
12635
12932
  incrementRepeatedFailures: true,
12933
+ onSucceeded: (succeededChunks) => {
12934
+ database.addChunksToBranchBatch(
12935
+ this.getBranchCatalogKey(),
12936
+ succeededChunks.map((chunk) => chunk.id)
12937
+ );
12938
+ },
12636
12939
  onProgress: (batchProgress) => onProgress?.({
12637
12940
  phase: "embedding",
12638
12941
  filesProcessed: unchangedFilePaths.size + processedChangedFiles,
@@ -12651,6 +12954,27 @@ var Indexer = class _Indexer {
12651
12954
  }
12652
12955
  }
12653
12956
  }
12957
+ for (const descriptor of descriptorBatch) {
12958
+ const existingFileChunks = existingChunksByFile.get(descriptor.storedPath);
12959
+ if (!existingFileChunks || existingFileChunks.size === 0) {
12960
+ committedFilePaths.add(descriptor.storedPath);
12961
+ }
12962
+ }
12963
+ const checkpointInterval = this.getCheckpointIntervalChunks(stats.totalChunks);
12964
+ if (stats.totalChunks - lastCheckpointChunks >= checkpointInterval) {
12965
+ lastCheckpointChunks = stats.totalChunks;
12966
+ this.checkpointIndexRun(
12967
+ database,
12968
+ store,
12969
+ invertedIndex,
12970
+ failedProcessing,
12971
+ resolvedRetryChunkIds,
12972
+ currentFileHashes,
12973
+ committedFilePaths,
12974
+ scopedRoots,
12975
+ configuredProviderInfo
12976
+ );
12977
+ }
12654
12978
  }
12655
12979
  const retryableFailedChunks = this.iterateLatestFailedChunks(
12656
12980
  failedProcessing.latestById,
@@ -12671,6 +12995,7 @@ var Indexer = class _Indexer {
12671
12995
  retryableChunksWithExistingData.add(chunk.id);
12672
12996
  }
12673
12997
  }
12998
+ this.restoreMissingChunkRows(database, pendingChunks);
12674
12999
  stats.totalChunks += pendingChunks.length;
12675
13000
  onProgress?.({
12676
13001
  phase: "embedding",
@@ -12693,6 +13018,17 @@ var Indexer = class _Indexer {
12693
13018
  forceReembed: forceScopedReembed,
12694
13019
  reuseCachedEmbeddings: true,
12695
13020
  incrementRepeatedFailures: true,
13021
+ forceSingleItemBatches: true,
13022
+ onSucceeded: (succeededChunks) => {
13023
+ database.addChunksToBranchBatch(
13024
+ this.getBranchCatalogKey(),
13025
+ succeededChunks.map((chunk) => chunk.id)
13026
+ );
13027
+ for (const chunk of succeededChunks) {
13028
+ failedProcessing.latestById.delete(chunk.id);
13029
+ resolvedRetryChunkIds.add(chunk.id);
13030
+ }
13031
+ },
12696
13032
  onProgress: (batchProgress) => onProgress?.({
12697
13033
  phase: "embedding",
12698
13034
  filesProcessed: files.length,
@@ -12710,6 +13046,20 @@ var Indexer = class _Indexer {
12710
13046
  failedForcedChunkIds.add(chunkId);
12711
13047
  }
12712
13048
  }
13049
+ if (stats.totalChunks - lastCheckpointChunks >= this.getCheckpointIntervalChunks(stats.totalChunks)) {
13050
+ lastCheckpointChunks = stats.totalChunks;
13051
+ this.checkpointIndexRun(
13052
+ database,
13053
+ store,
13054
+ invertedIndex,
13055
+ failedProcessing,
13056
+ resolvedRetryChunkIds,
13057
+ currentFileHashes,
13058
+ committedFilePaths,
13059
+ scopedRoots,
13060
+ configuredProviderInfo
13061
+ );
13062
+ }
12713
13063
  }
12714
13064
  const removedChunkIds = [];
12715
13065
  for (const [chunkId] of existingChunks) {
@@ -12746,13 +13096,6 @@ var Indexer = class _Indexer {
12746
13096
  if (removedStoredChunks) {
12747
13097
  this.saveInvertedIndex(invertedIndex);
12748
13098
  }
12749
- if (scopedRoots) {
12750
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12751
- } else {
12752
- this.fileHashCache = currentFileHashes;
12753
- this.saveFileHashCache();
12754
- }
12755
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12756
13099
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12757
13100
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12758
13101
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12761,6 +13104,13 @@ var Indexer = class _Indexer {
12761
13104
  this.indexCompatibility = { compatible: true };
12762
13105
  database.commitWriteTransaction();
12763
13106
  writeTransactionActive = false;
13107
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13108
+ if (scopedRoots) {
13109
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13110
+ } else {
13111
+ this.fileHashCache = currentFileHashes;
13112
+ this.saveFileHashCache();
13113
+ }
12764
13114
  stats.durationMs = Date.now() - startTime;
12765
13115
  onProgress?.({
12766
13116
  phase: "complete",
@@ -12784,13 +13134,6 @@ var Indexer = class _Indexer {
12784
13134
  );
12785
13135
  store.save();
12786
13136
  this.saveInvertedIndex(invertedIndex);
12787
- if (scopedRoots) {
12788
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12789
- } else {
12790
- this.fileHashCache = currentFileHashes;
12791
- this.saveFileHashCache();
12792
- }
12793
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12794
13137
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12795
13138
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12796
13139
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12799,6 +13142,13 @@ var Indexer = class _Indexer {
12799
13142
  this.indexCompatibility = { compatible: true };
12800
13143
  database.commitWriteTransaction();
12801
13144
  writeTransactionActive = false;
13145
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13146
+ if (scopedRoots) {
13147
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13148
+ } else {
13149
+ this.fileHashCache = currentFileHashes;
13150
+ this.saveFileHashCache();
13151
+ }
12802
13152
  stats.durationMs = Date.now() - startTime;
12803
13153
  onProgress?.({
12804
13154
  phase: "complete",
@@ -12833,15 +13183,15 @@ var Indexer = class _Indexer {
12833
13183
  );
12834
13184
  store.save();
12835
13185
  this.saveInvertedIndex(invertedIndex);
13186
+ database.commitWriteTransaction();
13187
+ writeTransactionActive = false;
13188
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
12836
13189
  if (scopedRoots) {
12837
13190
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12838
13191
  } else {
12839
13192
  this.fileHashCache = currentFileHashes;
12840
13193
  this.saveFileHashCache();
12841
13194
  }
12842
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12843
- database.commitWriteTransaction();
12844
- writeTransactionActive = false;
12845
13195
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
12846
13196
  const gcReset = await this.maybeRunOrphanGc();
12847
13197
  if (gcReset) {
@@ -12865,6 +13215,9 @@ var Indexer = class _Indexer {
12865
13215
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
12866
13216
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12867
13217
  }
13218
+ if (forceScopedReembed) {
13219
+ database.setMetadata(this.getProjectMigrationFinalizedMetadataKey(), "true");
13220
+ }
12868
13221
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12869
13222
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12870
13223
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12975,26 +13328,41 @@ var Indexer = class _Indexer {
12975
13328
  shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
12976
13329
  };
12977
13330
  }
12978
- searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
13331
+ searchCandidatesWithAllowedIds(initialLimit, totalCount, allowedChunkIds, shouldPrefilter, search, getChunkId) {
12979
13332
  const normalizedLimit = Math.max(0, Math.floor(initialLimit));
12980
13333
  if (normalizedLimit === 0) return [];
12981
- if (!shouldPrefilterByBranch || !branchChunkIds) {
13334
+ if (!shouldPrefilter || !allowedChunkIds) {
12982
13335
  return search(normalizedLimit);
12983
13336
  }
12984
- const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
13337
+ const targetCount = Math.min(normalizedLimit, allowedChunkIds.size);
12985
13338
  if (targetCount === 0 || totalCount === 0) return [];
12986
13339
  let requestedLimit = Math.min(normalizedLimit, totalCount);
12987
13340
  while (true) {
12988
13341
  const results = search(requestedLimit);
12989
- const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
12990
- if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
12991
- return branchResults;
13342
+ const allowedResults = results.filter((candidate) => allowedChunkIds.has(getChunkId(candidate)));
13343
+ if (allowedResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
13344
+ return allowedResults;
12992
13345
  }
12993
13346
  const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
12994
- if (nextLimit === requestedLimit) return branchResults;
13347
+ if (nextLimit === requestedLimit) return allowedResults;
12995
13348
  requestedLimit = nextLimit;
12996
13349
  }
12997
13350
  }
13351
+ getTemporalChunkIds(database, options) {
13352
+ if (!options?.blameSince && !options?.blameUntil) return null;
13353
+ const since = options.blameSince ? parseBlameTimestamp(options.blameSince, false) : void 0;
13354
+ const until = options.blameUntil ? parseBlameTimestamp(options.blameUntil, true) : void 0;
13355
+ if (since === null || until === null) {
13356
+ return /* @__PURE__ */ new Set();
13357
+ }
13358
+ return new Set(database.getChunkIdsByBlameDate(since, until));
13359
+ }
13360
+ intersectChunkIdSets(first, second) {
13361
+ if (first === null) return second;
13362
+ if (second === null) return first;
13363
+ const [smaller, larger] = first.size <= second.size ? [first, second] : [second, first];
13364
+ return new Set(Array.from(smaller).filter((chunkId) => larger.has(chunkId)));
13365
+ }
12998
13366
  buildCandidateSnapshot(candidate) {
12999
13367
  return {
13000
13368
  id: candidate.id,
@@ -13009,13 +13377,16 @@ var Indexer = class _Indexer {
13009
13377
  buildCandidateSnapshotList(candidates) {
13010
13378
  return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
13011
13379
  }
13012
- searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
13013
- return this.searchCandidatesWithBranchPrefilter(
13014
- initialLimit,
13015
- store.count(),
13380
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch, temporalChunkIds) {
13381
+ const availableCount = temporalChunkIds?.size ?? store.count();
13382
+ if (availableCount === 0) return [];
13383
+ const allowedIds = temporalChunkIds === null ? void 0 : Array.from(temporalChunkIds);
13384
+ return this.searchCandidatesWithAllowedIds(
13385
+ Math.min(initialLimit, availableCount),
13386
+ availableCount,
13016
13387
  branchChunkIds,
13017
13388
  shouldPrefilterByBranch,
13018
- (requestedLimit) => store.search(embedding, requestedLimit),
13389
+ (requestedLimit) => store.search(embedding, requestedLimit, allowedIds),
13019
13390
  (candidate) => candidate.id
13020
13391
  );
13021
13392
  }
@@ -13040,7 +13411,9 @@ var Indexer = class _Indexer {
13040
13411
  const rerankTopN = this.config.search.rerankTopN;
13041
13412
  const filterByBranch = options?.filterByBranch ?? true;
13042
13413
  const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
13414
+ const prioritizeSourcePaths = sourceIntent || options?.prioritizeSourcePaths === true;
13043
13415
  const identifierHints = extractIdentifierHints(query);
13416
+ const candidateLimit = maxResults * (prioritizeSourcePaths ? 12 : 4);
13044
13417
  this.logger.search("debug", "Starting search", {
13045
13418
  query,
13046
13419
  maxResults,
@@ -13071,25 +13444,28 @@ var Indexer = class _Indexer {
13071
13444
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
13072
13445
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
13073
13446
  }
13447
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
13074
13448
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
13075
13449
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
13076
13450
  const vectorStartTime = import_perf_hooks.performance.now();
13077
13451
  const semanticCandidates = embedding ? this.searchSemanticCandidates(
13078
13452
  store,
13079
13453
  embedding,
13080
- maxResults * 4,
13454
+ candidateLimit,
13081
13455
  branchChunkIds,
13082
- shouldPrefilterByBranch
13456
+ shouldPrefilterByBranch,
13457
+ temporalChunkIds
13083
13458
  ) : [];
13084
13459
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
13085
13460
  const keywordStartTime = import_perf_hooks.performance.now();
13086
13461
  const keywordCandidates = await this.keywordSearch(
13087
13462
  query,
13088
- maxResults * 4,
13463
+ candidateLimit,
13089
13464
  store,
13090
13465
  invertedIndex,
13091
13466
  branchChunkIds,
13092
- shouldPrefilterByBranch
13467
+ shouldPrefilterByBranch,
13468
+ temporalChunkIds
13093
13469
  );
13094
13470
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
13095
13471
  const scopedSemanticCandidates = semanticCandidates.filter(
@@ -13111,7 +13487,7 @@ var Indexer = class _Indexer {
13111
13487
  rerankTopN,
13112
13488
  limit: maxResults,
13113
13489
  hybridWeight: rankingHybridWeight,
13114
- prioritizeSourcePaths: sourceIntent
13490
+ prioritizeSourcePaths
13115
13491
  });
13116
13492
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
13117
13493
  definitionIntent: options?.definitionIntent === true,
@@ -13147,10 +13523,11 @@ var Indexer = class _Indexer {
13147
13523
  branchSymbolIds,
13148
13524
  maxResults,
13149
13525
  union,
13150
- sourceIntent
13526
+ sourceIntent,
13527
+ options?.definitionIntent === true && ((options.directory?.trim().length ?? 0) > 0 || (options.fileType?.trim().length ?? 0) > 0)
13151
13528
  );
13152
13529
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
13153
- const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13530
+ const primaryLane = options?.definitionIntent === true ? mergeTieredResults(symbolLane, prePrimaryLane, maxResults * 4) : mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13154
13531
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
13155
13532
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
13156
13533
  const baseFiltered = tiered.filter(
@@ -13245,14 +13622,18 @@ var Indexer = class _Indexer {
13245
13622
  })
13246
13623
  );
13247
13624
  }
13248
- async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
13625
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false, temporalChunkIds = null) {
13249
13626
  const normalizedLimit = Math.max(0, Math.floor(limit));
13250
13627
  if (normalizedLimit === 0) return [];
13251
- const scoreEntries = this.searchCandidatesWithBranchPrefilter(
13628
+ const allowedChunkIds = this.intersectChunkIdSets(
13629
+ shouldPrefilterByBranch ? branchChunkIds : null,
13630
+ temporalChunkIds
13631
+ );
13632
+ const scoreEntries = this.searchCandidatesWithAllowedIds(
13252
13633
  normalizedLimit,
13253
13634
  invertedIndex.getDocumentCount(),
13254
- branchChunkIds,
13255
- shouldPrefilterByBranch,
13635
+ allowedChunkIds,
13636
+ allowedChunkIds !== null,
13256
13637
  (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
13257
13638
  ([chunkId]) => chunkId
13258
13639
  );
@@ -13337,7 +13718,17 @@ var Indexer = class _Indexer {
13337
13718
  );
13338
13719
  const currentFileHashes = /* @__PURE__ */ new Map();
13339
13720
  for (const file of files) {
13340
- currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
13721
+ let hash;
13722
+ try {
13723
+ hash = hashFile(file.path);
13724
+ } catch (error) {
13725
+ this.logger.warn("Skipped unreadable file during freshness check", {
13726
+ path: file.path,
13727
+ error: getErrorMessage4(error)
13728
+ });
13729
+ return { readable: false, current: false, reason: "unreadable" };
13730
+ }
13731
+ currentFileHashes.set(this.toStoredFilePath(file.path), hash);
13341
13732
  }
13342
13733
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
13343
13734
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -13363,69 +13754,87 @@ var Indexer = class _Indexer {
13363
13754
  async forceIndex(onProgress) {
13364
13755
  return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
13365
13756
  await this.ensureInitializedUnlocked(recoveredOwners);
13366
- await this.clearIndexUnlocked();
13757
+ const recovery = this.beginClearRecoveryState();
13758
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13759
+ this.finishClearRecoveryState();
13367
13760
  return this.indexUnlocked(onProgress, [], true);
13368
13761
  });
13369
13762
  }
13370
13763
  async clearIndex() {
13371
13764
  await this.withIndexMutationLease("clear", async (recoveredOwners) => {
13372
13765
  await this.ensureInitializedUnlocked(recoveredOwners);
13373
- await this.clearIndexUnlocked();
13766
+ const recovery = this.beginClearRecoveryState();
13767
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13374
13768
  });
13375
13769
  }
13376
- async clearIndexUnlocked() {
13770
+ clearGlobalIndexDataUnlocked(projectRoot = this.projectRoot) {
13377
13771
  const { store, invertedIndex, database } = this.requireLoadedIndexState();
13378
- if (this.config.scope === "global") {
13379
- store.load();
13380
- invertedIndex.load();
13381
- this.loadFileHashCache();
13382
- const roots = this.getScopedRoots();
13383
- const compatibility = this.checkCompatibility();
13384
- const allMetadata = store.getAllMetadata();
13385
- const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13386
- if (!compatibility.compatible && hasForeignData) {
13387
- if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
13388
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13389
- this.clearScopedFileHashCache(roots);
13390
- this.clearScopedFailedBatches(roots);
13391
- database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
13392
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13772
+ const clearedBranchKeys = database.getAllBranches();
13773
+ store.clear();
13774
+ store.save();
13775
+ invertedIndex.clear();
13776
+ this.saveInvertedIndex(invertedIndex);
13777
+ this.fileHashCache.clear();
13778
+ this.saveFileHashCache();
13779
+ database.clearAllIndexedData();
13780
+ this.deleteBranchCommitMetadata(database, clearedBranchKeys);
13781
+ this.clearFailedBatchState();
13782
+ database.deleteMetadata("index.version");
13783
+ database.deleteMetadata("index.pathStorageVersion");
13784
+ database.deleteMetadata("index.embeddingProvider");
13785
+ database.deleteMetadata("index.embeddingModel");
13786
+ database.deleteMetadata("index.embeddingDimensions");
13787
+ database.deleteMetadata("index.embeddingStrategyVersion");
13788
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13789
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13790
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash));
13791
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey(projectIdentityHash));
13792
+ database.deleteMetadata("index.createdAt");
13793
+ database.deleteMetadata("index.updatedAt");
13794
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13795
+ }
13796
+ clearGlobalIndexUnlocked(projectRoot = this.projectRoot, roots = this.getScopedRoots(), recoveryDecision) {
13797
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13798
+ store.load();
13799
+ invertedIndex.load();
13800
+ this.loadFileHashCache();
13801
+ const compatibility = this.checkCompatibility();
13802
+ const compatibilityDecision = recoveryDecision ?? (compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible");
13803
+ const allMetadata = store.getAllMetadata();
13804
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData(projectRoot, roots) || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13805
+ if (compatibilityDecision !== "compatible" && hasForeignData) {
13806
+ if (compatibilityDecision === "embedding-strategy-mismatch") {
13807
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13808
+ this.clearScopedFileHashCache(roots);
13809
+ this.clearScopedFailedBatches(roots);
13810
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13811
+ database.setMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash), "true");
13812
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13813
+ database.deleteMetadata(this.getProjectMigrationFinalizedMetadataKey(projectIdentityHash));
13814
+ if (projectRoot === this.projectRoot) {
13393
13815
  this.indexCompatibility = { compatible: true };
13394
- return;
13395
13816
  }
13396
- throw new Error(
13397
- `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.`
13398
- );
13399
- }
13400
- if (!hasForeignData) {
13401
- const clearedBranchKeys2 = database.getAllBranches();
13402
- store.clear();
13403
- store.save();
13404
- invertedIndex.clear();
13405
- this.saveInvertedIndex(invertedIndex);
13406
- this.fileHashCache.clear();
13407
- this.saveFileHashCache();
13408
- database.clearAllIndexedData();
13409
- this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
13410
- this.clearFailedBatchState();
13411
- database.deleteMetadata("index.version");
13412
- database.deleteMetadata("index.pathStorageVersion");
13413
- database.deleteMetadata("index.embeddingProvider");
13414
- database.deleteMetadata("index.embeddingModel");
13415
- database.deleteMetadata("index.embeddingDimensions");
13416
- database.deleteMetadata("index.embeddingStrategyVersion");
13417
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13418
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13419
- database.deleteMetadata(this.getLegacyMigrationMetadataKey());
13420
- database.deleteMetadata("index.createdAt");
13421
- database.deleteMetadata("index.updatedAt");
13422
- this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13423
13817
  return;
13424
13818
  }
13425
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13426
- this.clearScopedFileHashCache(roots);
13427
- this.clearScopedFailedBatches(roots);
13819
+ throw new Error(
13820
+ `Global index compatibility reset is unsafe because the shared index contains files from other projects. The current global index cannot be force-rebuilt for ${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.`
13821
+ );
13822
+ }
13823
+ if (!hasForeignData) {
13824
+ this.clearGlobalIndexDataUnlocked(projectRoot);
13825
+ return;
13826
+ }
13827
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13828
+ this.clearScopedFileHashCache(roots);
13829
+ this.clearScopedFailedBatches(roots);
13830
+ if (projectRoot === this.projectRoot) {
13428
13831
  this.indexCompatibility = compatibility;
13832
+ }
13833
+ }
13834
+ async clearIndexUnlocked(recoveryDecision) {
13835
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13836
+ if (this.config.scope === "global") {
13837
+ this.clearGlobalIndexUnlocked(this.projectRoot, this.getScopedRoots(), recoveryDecision);
13429
13838
  return;
13430
13839
  }
13431
13840
  if (!this.isProjectOwnedIndexPath()) {
@@ -13591,6 +14000,7 @@ var Indexer = class _Indexer {
13591
14000
  )) {
13592
14001
  const chunks = retryBatch.map(({ chunk }) => chunk);
13593
14002
  const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
14003
+ this.restoreMissingChunkRows(database, chunks);
13594
14004
  const batchResult = await this.processPendingChunkBatch(chunks, {
13595
14005
  store,
13596
14006
  provider,
@@ -13605,6 +14015,7 @@ var Indexer = class _Indexer {
13605
14015
  forceReembed: false,
13606
14016
  reuseCachedEmbeddings: false,
13607
14017
  incrementRepeatedFailures: false,
14018
+ forceSingleItemBatches: true,
13608
14019
  onSucceeded: (succeededChunks) => {
13609
14020
  database.addChunksToBranchBatch(
13610
14021
  this.getBranchCatalogKey(),
@@ -13626,9 +14037,12 @@ var Indexer = class _Indexer {
13626
14037
  this.saveInvertedIndex(invertedIndex);
13627
14038
  }
13628
14039
  if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
13629
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13630
- this.saveIndexMetadata(configuredProviderInfo);
13631
- this.indexCompatibility = { compatible: true };
14040
+ const migrationFinalized = database.getMetadata(this.getProjectMigrationFinalizedMetadataKey()) === "true";
14041
+ if (migrationFinalized) {
14042
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
14043
+ this.saveIndexMetadata(configuredProviderInfo);
14044
+ this.indexCompatibility = { compatible: true };
14045
+ }
13632
14046
  }
13633
14047
  return { succeeded, failed, remaining };
13634
14048
  }
@@ -13650,7 +14064,8 @@ var Indexer = class _Indexer {
13650
14064
  latestById.set(chunkId, {
13651
14065
  attemptCount: batch.attemptCount,
13652
14066
  error: batch.error,
13653
- lastAttempt: batch.lastAttempt
14067
+ lastAttempt: batch.lastAttempt,
14068
+ chunks: [rawChunk]
13654
14069
  });
13655
14070
  }
13656
14071
  }
@@ -13717,6 +14132,7 @@ var Indexer = class _Indexer {
13717
14132
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
13718
14133
  );
13719
14134
  }
14135
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
13720
14136
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
13721
14137
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
13722
14138
  const vectorStartTime = import_perf_hooks.performance.now();
@@ -13725,7 +14141,8 @@ var Indexer = class _Indexer {
13725
14141
  embedding,
13726
14142
  limit * 2,
13727
14143
  branchChunkIds,
13728
- shouldPrefilterByBranch
14144
+ shouldPrefilterByBranch,
14145
+ temporalChunkIds
13729
14146
  );
13730
14147
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
13731
14148
  if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
@@ -13843,9 +14260,9 @@ var Indexer = class _Indexer {
13843
14260
  this.requireReadableComponents(readIssues, "database");
13844
14261
  let shortest = [];
13845
14262
  for (const branchKey of this.getBranchCatalogKeys()) {
13846
- const path28 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
13847
- if (path28.length > 0 && (shortest.length === 0 || path28.length < shortest.length)) {
13848
- shortest = path28;
14263
+ const path30 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
14264
+ if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14265
+ shortest = path30;
13849
14266
  }
13850
14267
  }
13851
14268
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -13893,13 +14310,13 @@ var Indexer = class _Indexer {
13893
14310
  }
13894
14311
  }
13895
14312
  if (!found) continue;
13896
- const path28 = [];
14313
+ const path30 = [];
13897
14314
  let currentSymbolId = toSymbolId;
13898
14315
  while (true) {
13899
14316
  const symbol = symbolsById.get(currentSymbolId);
13900
14317
  if (!symbol) break;
13901
14318
  const parent = parentBySymbolId.get(currentSymbolId);
13902
- path28.push({
14319
+ path30.push({
13903
14320
  symbolId: symbol.id,
13904
14321
  symbolName: symbol.name,
13905
14322
  filePath: symbol.filePath,
@@ -13909,9 +14326,9 @@ var Indexer = class _Indexer {
13909
14326
  if (!parent) break;
13910
14327
  currentSymbolId = parent.parentId;
13911
14328
  }
13912
- path28.reverse();
13913
- if (path28.length > 0 && (shortest.length === 0 || path28.length < shortest.length)) {
13914
- shortest = path28;
14329
+ path30.reverse();
14330
+ if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14331
+ shortest = path30;
13915
14332
  }
13916
14333
  }
13917
14334
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14247,7 +14664,6 @@ var Indexer = class _Indexer {
14247
14664
  this.store = null;
14248
14665
  this.invertedIndex = null;
14249
14666
  this.provider = null;
14250
- this.reranker = null;
14251
14667
  this.configuredProviderInfo = null;
14252
14668
  this.indexCompatibility = null;
14253
14669
  this.initializationMode = "none";
@@ -14475,9 +14891,11 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14475
14891
  contextLines: options.contextLines,
14476
14892
  metadataOnly: options.metadataOnly,
14477
14893
  definitionIntent: options.definitionIntent,
14894
+ prioritizeSourcePaths: options.prioritizeSourcePaths,
14478
14895
  blameAuthor: options.blameAuthor,
14479
14896
  blameSha: options.blameSha,
14480
14897
  blameSince: options.blameSince,
14898
+ blameUntil: options.blameUntil,
14481
14899
  trace: options.trace
14482
14900
  });
14483
14901
  }
@@ -14523,7 +14941,9 @@ async function findSimilarCode(projectRoot, host, code, options = {}) {
14523
14941
  fileType: options.fileType,
14524
14942
  directory: options.directory,
14525
14943
  chunkType: options.chunkType,
14526
- excludeFile: options.excludeFile
14944
+ excludeFile: options.excludeFile,
14945
+ blameSince: options.blameSince,
14946
+ blameUntil: options.blameUntil
14527
14947
  });
14528
14948
  }
14529
14949
  async function implementationLookup(projectRoot, host, query, options = {}) {
@@ -14572,12 +14992,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14572
14992
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14573
14993
  return { from: fromResolution, to: toResolution, path: [] };
14574
14994
  }
14575
- const path28 = await indexer.findCallPathBySymbolIds(
14995
+ const path30 = await indexer.findCallPathBySymbolIds(
14576
14996
  fromResolution.symbolId,
14577
14997
  toResolution.symbolId,
14578
14998
  maxDepth
14579
14999
  );
14580
- return { from: fromResolution, to: toResolution, path: path28 };
15000
+ return { from: fromResolution, to: toResolution, path: path30 };
14581
15001
  }
14582
15002
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
14583
15003
  const root = getProjectRoot(projectRoot, host);
@@ -14803,8 +15223,8 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
14803
15223
  }
14804
15224
  }
14805
15225
  try {
14806
- const stat4 = (0, import_fs13.statSync)(normalizedPath2);
14807
- if (!stat4.isDirectory()) {
15226
+ const stat5 = (0, import_fs13.statSync)(normalizedPath2);
15227
+ if (!stat5.isDirectory()) {
14808
15228
  return `Error: Path is not a directory: ${normalizedPath2}`;
14809
15229
  }
14810
15230
  } catch (error) {
@@ -14852,8 +15272,8 @@ function listKnowledgeBases(projectRoot, host) {
14852
15272
  `;
14853
15273
  if (exists) {
14854
15274
  try {
14855
- const stat4 = (0, import_fs13.statSync)(resolvedPath);
14856
- result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
15275
+ const stat5 = (0, import_fs13.statSync)(resolvedPath);
15276
+ result += ` Type: ${stat5.isDirectory() ? "Directory" : "File"}
14857
15277
  `;
14858
15278
  } catch {
14859
15279
  }
@@ -14986,7 +15406,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
14986
15406
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
14987
15407
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
14988
15408
  if (wantBigintFsStats) {
14989
- this._stat = (path28) => statMethod(path28, { bigint: true });
15409
+ this._stat = (path30) => statMethod(path30, { bigint: true });
14990
15410
  } else {
14991
15411
  this._stat = statMethod;
14992
15412
  }
@@ -15011,8 +15431,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
15011
15431
  const par = this.parent;
15012
15432
  const fil = par && par.files;
15013
15433
  if (fil && fil.length > 0) {
15014
- const { path: path28, depth } = par;
15015
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path28));
15434
+ const { path: path30, depth } = par;
15435
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path30));
15016
15436
  const awaited = await Promise.all(slice);
15017
15437
  for (const entry of awaited) {
15018
15438
  if (!entry)
@@ -15052,20 +15472,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
15052
15472
  this.reading = false;
15053
15473
  }
15054
15474
  }
15055
- async _exploreDir(path28, depth) {
15475
+ async _exploreDir(path30, depth) {
15056
15476
  let files;
15057
15477
  try {
15058
- files = await (0, import_promises.readdir)(path28, this._rdOptions);
15478
+ files = await (0, import_promises.readdir)(path30, this._rdOptions);
15059
15479
  } catch (error) {
15060
15480
  this._onError(error);
15061
15481
  }
15062
- return { files, depth, path: path28 };
15482
+ return { files, depth, path: path30 };
15063
15483
  }
15064
- async _formatEntry(dirent, path28) {
15484
+ async _formatEntry(dirent, path30) {
15065
15485
  let entry;
15066
15486
  const basename9 = this._isDirent ? dirent.name : dirent;
15067
15487
  try {
15068
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path28, basename9));
15488
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path30, basename9));
15069
15489
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename9 };
15070
15490
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
15071
15491
  } catch (err) {
@@ -15465,16 +15885,16 @@ var delFromSet = (main, prop, item) => {
15465
15885
  };
15466
15886
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
15467
15887
  var FsWatchInstances = /* @__PURE__ */ new Map();
15468
- function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
15888
+ function createFsWatchInstance(path30, options, listener, errHandler, emitRaw) {
15469
15889
  const handleEvent = (rawEvent, evPath) => {
15470
- listener(path28);
15471
- emitRaw(rawEvent, evPath, { watchedPath: path28 });
15472
- if (evPath && path28 !== evPath) {
15473
- fsWatchBroadcast(sp.resolve(path28, evPath), KEY_LISTENERS, sp.join(path28, evPath));
15890
+ listener(path30);
15891
+ emitRaw(rawEvent, evPath, { watchedPath: path30 });
15892
+ if (evPath && path30 !== evPath) {
15893
+ fsWatchBroadcast(sp.resolve(path30, evPath), KEY_LISTENERS, sp.join(path30, evPath));
15474
15894
  }
15475
15895
  };
15476
15896
  try {
15477
- return (0, import_node_fs.watch)(path28, {
15897
+ return (0, import_node_fs.watch)(path30, {
15478
15898
  persistent: options.persistent
15479
15899
  }, handleEvent);
15480
15900
  } catch (error) {
@@ -15490,12 +15910,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
15490
15910
  listener(val1, val2, val3);
15491
15911
  });
15492
15912
  };
15493
- var setFsWatchListener = (path28, fullPath, options, handlers) => {
15913
+ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15494
15914
  const { listener, errHandler, rawEmitter } = handlers;
15495
15915
  let cont = FsWatchInstances.get(fullPath);
15496
15916
  let watcher;
15497
15917
  if (!options.persistent) {
15498
- watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
15918
+ watcher = createFsWatchInstance(path30, options, listener, errHandler, rawEmitter);
15499
15919
  if (!watcher)
15500
15920
  return;
15501
15921
  return watcher.close.bind(watcher);
@@ -15506,7 +15926,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15506
15926
  addAndConvert(cont, KEY_RAW, rawEmitter);
15507
15927
  } else {
15508
15928
  watcher = createFsWatchInstance(
15509
- path28,
15929
+ path30,
15510
15930
  options,
15511
15931
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
15512
15932
  errHandler,
@@ -15521,7 +15941,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15521
15941
  cont.watcherUnusable = true;
15522
15942
  if (isWindows && error.code === "EPERM") {
15523
15943
  try {
15524
- const fd = await (0, import_promises2.open)(path28, "r");
15944
+ const fd = await (0, import_promises2.open)(path30, "r");
15525
15945
  await fd.close();
15526
15946
  broadcastErr(error);
15527
15947
  } catch (err) {
@@ -15552,7 +15972,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15552
15972
  };
15553
15973
  };
15554
15974
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
15555
- var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
15975
+ var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
15556
15976
  const { listener, rawEmitter } = handlers;
15557
15977
  let cont = FsWatchFileInstances.get(fullPath);
15558
15978
  const copts = cont && cont.options;
@@ -15574,7 +15994,7 @@ var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
15574
15994
  });
15575
15995
  const currmtime = curr.mtimeMs;
15576
15996
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
15577
- foreach(cont.listeners, (listener2) => listener2(path28, curr));
15997
+ foreach(cont.listeners, (listener2) => listener2(path30, curr));
15578
15998
  }
15579
15999
  })
15580
16000
  };
@@ -15604,13 +16024,13 @@ var NodeFsHandler = class {
15604
16024
  * @param listener on fs change
15605
16025
  * @returns closer for the watcher instance
15606
16026
  */
15607
- _watchWithNodeFs(path28, listener) {
16027
+ _watchWithNodeFs(path30, listener) {
15608
16028
  const opts = this.fsw.options;
15609
- const directory = sp.dirname(path28);
15610
- const basename9 = sp.basename(path28);
16029
+ const directory = sp.dirname(path30);
16030
+ const basename9 = sp.basename(path30);
15611
16031
  const parent = this.fsw._getWatchedDir(directory);
15612
16032
  parent.add(basename9);
15613
- const absolutePath = sp.resolve(path28);
16033
+ const absolutePath = sp.resolve(path30);
15614
16034
  const options = {
15615
16035
  persistent: opts.persistent
15616
16036
  };
@@ -15620,12 +16040,12 @@ var NodeFsHandler = class {
15620
16040
  if (opts.usePolling) {
15621
16041
  const enableBin = opts.interval !== opts.binaryInterval;
15622
16042
  options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
15623
- closer = setFsWatchFileListener(path28, absolutePath, options, {
16043
+ closer = setFsWatchFileListener(path30, absolutePath, options, {
15624
16044
  listener,
15625
16045
  rawEmitter: this.fsw._emitRaw
15626
16046
  });
15627
16047
  } else {
15628
- closer = setFsWatchListener(path28, absolutePath, options, {
16048
+ closer = setFsWatchListener(path30, absolutePath, options, {
15629
16049
  listener,
15630
16050
  errHandler: this._boundHandleError,
15631
16051
  rawEmitter: this.fsw._emitRaw
@@ -15647,7 +16067,7 @@ var NodeFsHandler = class {
15647
16067
  let prevStats = stats;
15648
16068
  if (parent.has(basename9))
15649
16069
  return;
15650
- const listener = async (path28, newStats) => {
16070
+ const listener = async (path30, newStats) => {
15651
16071
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
15652
16072
  return;
15653
16073
  if (!newStats || newStats.mtimeMs === 0) {
@@ -15661,11 +16081,11 @@ var NodeFsHandler = class {
15661
16081
  this.fsw._emit(EV.CHANGE, file, newStats2);
15662
16082
  }
15663
16083
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
15664
- this.fsw._closeFile(path28);
16084
+ this.fsw._closeFile(path30);
15665
16085
  prevStats = newStats2;
15666
16086
  const closer2 = this._watchWithNodeFs(file, listener);
15667
16087
  if (closer2)
15668
- this.fsw._addPathCloser(path28, closer2);
16088
+ this.fsw._addPathCloser(path30, closer2);
15669
16089
  } else {
15670
16090
  prevStats = newStats2;
15671
16091
  }
@@ -15697,7 +16117,7 @@ var NodeFsHandler = class {
15697
16117
  * @param item basename of this item
15698
16118
  * @returns true if no more processing is needed for this entry.
15699
16119
  */
15700
- async _handleSymlink(entry, directory, path28, item) {
16120
+ async _handleSymlink(entry, directory, path30, item) {
15701
16121
  if (this.fsw.closed) {
15702
16122
  return;
15703
16123
  }
@@ -15707,7 +16127,7 @@ var NodeFsHandler = class {
15707
16127
  this.fsw._incrReadyCount();
15708
16128
  let linkPath;
15709
16129
  try {
15710
- linkPath = await (0, import_promises2.realpath)(path28);
16130
+ linkPath = await (0, import_promises2.realpath)(path30);
15711
16131
  } catch (e) {
15712
16132
  this.fsw._emitReady();
15713
16133
  return true;
@@ -15717,12 +16137,12 @@ var NodeFsHandler = class {
15717
16137
  if (dir.has(item)) {
15718
16138
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
15719
16139
  this.fsw._symlinkPaths.set(full, linkPath);
15720
- this.fsw._emit(EV.CHANGE, path28, entry.stats);
16140
+ this.fsw._emit(EV.CHANGE, path30, entry.stats);
15721
16141
  }
15722
16142
  } else {
15723
16143
  dir.add(item);
15724
16144
  this.fsw._symlinkPaths.set(full, linkPath);
15725
- this.fsw._emit(EV.ADD, path28, entry.stats);
16145
+ this.fsw._emit(EV.ADD, path30, entry.stats);
15726
16146
  }
15727
16147
  this.fsw._emitReady();
15728
16148
  return true;
@@ -15752,9 +16172,9 @@ var NodeFsHandler = class {
15752
16172
  return;
15753
16173
  }
15754
16174
  const item = entry.path;
15755
- let path28 = sp.join(directory, item);
16175
+ let path30 = sp.join(directory, item);
15756
16176
  current.add(item);
15757
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
16177
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path30, item)) {
15758
16178
  return;
15759
16179
  }
15760
16180
  if (this.fsw.closed) {
@@ -15763,11 +16183,11 @@ var NodeFsHandler = class {
15763
16183
  }
15764
16184
  if (item === target || !target && !previous.has(item)) {
15765
16185
  this.fsw._incrReadyCount();
15766
- path28 = sp.join(dir, sp.relative(dir, path28));
15767
- this._addToNodeFs(path28, initialAdd, wh, depth + 1);
16186
+ path30 = sp.join(dir, sp.relative(dir, path30));
16187
+ this._addToNodeFs(path30, initialAdd, wh, depth + 1);
15768
16188
  }
15769
16189
  }).on(EV.ERROR, this._boundHandleError);
15770
- return new Promise((resolve15, reject) => {
16190
+ return new Promise((resolve17, reject) => {
15771
16191
  if (!stream)
15772
16192
  return reject();
15773
16193
  stream.once(STR_END, () => {
@@ -15776,7 +16196,7 @@ var NodeFsHandler = class {
15776
16196
  return;
15777
16197
  }
15778
16198
  const wasThrottled = throttler ? throttler.clear() : false;
15779
- resolve15(void 0);
16199
+ resolve17(void 0);
15780
16200
  previous.getChildren().filter((item) => {
15781
16201
  return item !== directory && !current.has(item);
15782
16202
  }).forEach((item) => {
@@ -15833,13 +16253,13 @@ var NodeFsHandler = class {
15833
16253
  * @param depth Child path actually targeted for watch
15834
16254
  * @param target Child path actually targeted for watch
15835
16255
  */
15836
- async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
16256
+ async _addToNodeFs(path30, initialAdd, priorWh, depth, target) {
15837
16257
  const ready = this.fsw._emitReady;
15838
- if (this.fsw._isIgnored(path28) || this.fsw.closed) {
16258
+ if (this.fsw._isIgnored(path30) || this.fsw.closed) {
15839
16259
  ready();
15840
16260
  return false;
15841
16261
  }
15842
- const wh = this.fsw._getWatchHelpers(path28);
16262
+ const wh = this.fsw._getWatchHelpers(path30);
15843
16263
  if (priorWh) {
15844
16264
  wh.filterPath = (entry) => priorWh.filterPath(entry);
15845
16265
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -15855,8 +16275,8 @@ var NodeFsHandler = class {
15855
16275
  const follow = this.fsw.options.followSymlinks;
15856
16276
  let closer;
15857
16277
  if (stats.isDirectory()) {
15858
- const absPath = sp.resolve(path28);
15859
- const targetPath = follow ? await (0, import_promises2.realpath)(path28) : path28;
16278
+ const absPath = sp.resolve(path30);
16279
+ const targetPath = follow ? await (0, import_promises2.realpath)(path30) : path30;
15860
16280
  if (this.fsw.closed)
15861
16281
  return;
15862
16282
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -15866,29 +16286,29 @@ var NodeFsHandler = class {
15866
16286
  this.fsw._symlinkPaths.set(absPath, targetPath);
15867
16287
  }
15868
16288
  } else if (stats.isSymbolicLink()) {
15869
- const targetPath = follow ? await (0, import_promises2.realpath)(path28) : path28;
16289
+ const targetPath = follow ? await (0, import_promises2.realpath)(path30) : path30;
15870
16290
  if (this.fsw.closed)
15871
16291
  return;
15872
16292
  const parent = sp.dirname(wh.watchPath);
15873
16293
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
15874
16294
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
15875
- closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
16295
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path30, wh, targetPath);
15876
16296
  if (this.fsw.closed)
15877
16297
  return;
15878
16298
  if (targetPath !== void 0) {
15879
- this.fsw._symlinkPaths.set(sp.resolve(path28), targetPath);
16299
+ this.fsw._symlinkPaths.set(sp.resolve(path30), targetPath);
15880
16300
  }
15881
16301
  } else {
15882
16302
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
15883
16303
  }
15884
16304
  ready();
15885
16305
  if (closer)
15886
- this.fsw._addPathCloser(path28, closer);
16306
+ this.fsw._addPathCloser(path30, closer);
15887
16307
  return false;
15888
16308
  } catch (error) {
15889
16309
  if (this.fsw._handleError(error)) {
15890
16310
  ready();
15891
- return path28;
16311
+ return path30;
15892
16312
  }
15893
16313
  }
15894
16314
  }
@@ -15920,35 +16340,35 @@ function createPattern(matcher) {
15920
16340
  if (matcher.path === string)
15921
16341
  return true;
15922
16342
  if (matcher.recursive) {
15923
- const relative12 = sp2.relative(matcher.path, string);
15924
- if (!relative12) {
16343
+ const relative14 = sp2.relative(matcher.path, string);
16344
+ if (!relative14) {
15925
16345
  return false;
15926
16346
  }
15927
- return !relative12.startsWith("..") && !sp2.isAbsolute(relative12);
16347
+ return !relative14.startsWith("..") && !sp2.isAbsolute(relative14);
15928
16348
  }
15929
16349
  return false;
15930
16350
  };
15931
16351
  }
15932
16352
  return () => false;
15933
16353
  }
15934
- function normalizePath2(path28) {
15935
- if (typeof path28 !== "string")
16354
+ function normalizePath2(path30) {
16355
+ if (typeof path30 !== "string")
15936
16356
  throw new Error("string expected");
15937
- path28 = sp2.normalize(path28);
15938
- path28 = path28.replace(/\\/g, "/");
16357
+ path30 = sp2.normalize(path30);
16358
+ path30 = path30.replace(/\\/g, "/");
15939
16359
  let prepend = false;
15940
- if (path28.startsWith("//"))
16360
+ if (path30.startsWith("//"))
15941
16361
  prepend = true;
15942
- path28 = path28.replace(DOUBLE_SLASH_RE, "/");
16362
+ path30 = path30.replace(DOUBLE_SLASH_RE, "/");
15943
16363
  if (prepend)
15944
- path28 = "/" + path28;
15945
- return path28;
16364
+ path30 = "/" + path30;
16365
+ return path30;
15946
16366
  }
15947
16367
  function matchPatterns(patterns, testString, stats) {
15948
- const path28 = normalizePath2(testString);
16368
+ const path30 = normalizePath2(testString);
15949
16369
  for (let index = 0; index < patterns.length; index++) {
15950
16370
  const pattern = patterns[index];
15951
- if (pattern(path28, stats)) {
16371
+ if (pattern(path30, stats)) {
15952
16372
  return true;
15953
16373
  }
15954
16374
  }
@@ -15986,19 +16406,19 @@ var toUnix = (string) => {
15986
16406
  }
15987
16407
  return str;
15988
16408
  };
15989
- var normalizePathToUnix = (path28) => toUnix(sp2.normalize(toUnix(path28)));
15990
- var normalizeIgnored = (cwd = "") => (path28) => {
15991
- if (typeof path28 === "string") {
15992
- return normalizePathToUnix(sp2.isAbsolute(path28) ? path28 : sp2.join(cwd, path28));
16409
+ var normalizePathToUnix = (path30) => toUnix(sp2.normalize(toUnix(path30)));
16410
+ var normalizeIgnored = (cwd = "") => (path30) => {
16411
+ if (typeof path30 === "string") {
16412
+ return normalizePathToUnix(sp2.isAbsolute(path30) ? path30 : sp2.join(cwd, path30));
15993
16413
  } else {
15994
- return path28;
16414
+ return path30;
15995
16415
  }
15996
16416
  };
15997
- var getAbsolutePath = (path28, cwd) => {
15998
- if (sp2.isAbsolute(path28)) {
15999
- return path28;
16417
+ var getAbsolutePath = (path30, cwd) => {
16418
+ if (sp2.isAbsolute(path30)) {
16419
+ return path30;
16000
16420
  }
16001
- return sp2.join(cwd, path28);
16421
+ return sp2.join(cwd, path30);
16002
16422
  };
16003
16423
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
16004
16424
  var DirEntry = class {
@@ -16063,10 +16483,10 @@ var WatchHelper = class {
16063
16483
  dirParts;
16064
16484
  followSymlinks;
16065
16485
  statMethod;
16066
- constructor(path28, follow, fsw) {
16486
+ constructor(path30, follow, fsw) {
16067
16487
  this.fsw = fsw;
16068
- const watchPath = path28;
16069
- this.path = path28 = path28.replace(REPLACER_RE, "");
16488
+ const watchPath = path30;
16489
+ this.path = path30 = path30.replace(REPLACER_RE, "");
16070
16490
  this.watchPath = watchPath;
16071
16491
  this.fullWatchPath = sp2.resolve(watchPath);
16072
16492
  this.dirParts = [];
@@ -16206,20 +16626,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16206
16626
  this._closePromise = void 0;
16207
16627
  let paths = unifyPaths(paths_);
16208
16628
  if (cwd) {
16209
- paths = paths.map((path28) => {
16210
- const absPath = getAbsolutePath(path28, cwd);
16629
+ paths = paths.map((path30) => {
16630
+ const absPath = getAbsolutePath(path30, cwd);
16211
16631
  return absPath;
16212
16632
  });
16213
16633
  }
16214
- paths.forEach((path28) => {
16215
- this._removeIgnoredPath(path28);
16634
+ paths.forEach((path30) => {
16635
+ this._removeIgnoredPath(path30);
16216
16636
  });
16217
16637
  this._userIgnored = void 0;
16218
16638
  if (!this._readyCount)
16219
16639
  this._readyCount = 0;
16220
16640
  this._readyCount += paths.length;
16221
- Promise.all(paths.map(async (path28) => {
16222
- const res = await this._nodeFsHandler._addToNodeFs(path28, !_internal, void 0, 0, _origAdd);
16641
+ Promise.all(paths.map(async (path30) => {
16642
+ const res = await this._nodeFsHandler._addToNodeFs(path30, !_internal, void 0, 0, _origAdd);
16223
16643
  if (res)
16224
16644
  this._emitReady();
16225
16645
  return res;
@@ -16241,17 +16661,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16241
16661
  return this;
16242
16662
  const paths = unifyPaths(paths_);
16243
16663
  const { cwd } = this.options;
16244
- paths.forEach((path28) => {
16245
- if (!sp2.isAbsolute(path28) && !this._closers.has(path28)) {
16664
+ paths.forEach((path30) => {
16665
+ if (!sp2.isAbsolute(path30) && !this._closers.has(path30)) {
16246
16666
  if (cwd)
16247
- path28 = sp2.join(cwd, path28);
16248
- path28 = sp2.resolve(path28);
16667
+ path30 = sp2.join(cwd, path30);
16668
+ path30 = sp2.resolve(path30);
16249
16669
  }
16250
- this._closePath(path28);
16251
- this._addIgnoredPath(path28);
16252
- if (this._watched.has(path28)) {
16670
+ this._closePath(path30);
16671
+ this._addIgnoredPath(path30);
16672
+ if (this._watched.has(path30)) {
16253
16673
  this._addIgnoredPath({
16254
- path: path28,
16674
+ path: path30,
16255
16675
  recursive: true
16256
16676
  });
16257
16677
  }
@@ -16315,38 +16735,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16315
16735
  * @param stats arguments to be passed with event
16316
16736
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
16317
16737
  */
16318
- async _emit(event, path28, stats) {
16738
+ async _emit(event, path30, stats) {
16319
16739
  if (this.closed)
16320
16740
  return;
16321
16741
  const opts = this.options;
16322
16742
  if (isWindows)
16323
- path28 = sp2.normalize(path28);
16743
+ path30 = sp2.normalize(path30);
16324
16744
  if (opts.cwd)
16325
- path28 = sp2.relative(opts.cwd, path28);
16326
- const args = [path28];
16745
+ path30 = sp2.relative(opts.cwd, path30);
16746
+ const args = [path30];
16327
16747
  if (stats != null)
16328
16748
  args.push(stats);
16329
16749
  const awf = opts.awaitWriteFinish;
16330
16750
  let pw;
16331
- if (awf && (pw = this._pendingWrites.get(path28))) {
16751
+ if (awf && (pw = this._pendingWrites.get(path30))) {
16332
16752
  pw.lastChange = /* @__PURE__ */ new Date();
16333
16753
  return this;
16334
16754
  }
16335
16755
  if (opts.atomic) {
16336
16756
  if (event === EVENTS.UNLINK) {
16337
- this._pendingUnlinks.set(path28, [event, ...args]);
16757
+ this._pendingUnlinks.set(path30, [event, ...args]);
16338
16758
  setTimeout(() => {
16339
- this._pendingUnlinks.forEach((entry, path29) => {
16759
+ this._pendingUnlinks.forEach((entry, path31) => {
16340
16760
  this.emit(...entry);
16341
16761
  this.emit(EVENTS.ALL, ...entry);
16342
- this._pendingUnlinks.delete(path29);
16762
+ this._pendingUnlinks.delete(path31);
16343
16763
  });
16344
16764
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
16345
16765
  return this;
16346
16766
  }
16347
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
16767
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path30)) {
16348
16768
  event = EVENTS.CHANGE;
16349
- this._pendingUnlinks.delete(path28);
16769
+ this._pendingUnlinks.delete(path30);
16350
16770
  }
16351
16771
  }
16352
16772
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -16364,16 +16784,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16364
16784
  this.emitWithAll(event, args);
16365
16785
  }
16366
16786
  };
16367
- this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
16787
+ this._awaitWriteFinish(path30, awf.stabilityThreshold, event, awfEmit);
16368
16788
  return this;
16369
16789
  }
16370
16790
  if (event === EVENTS.CHANGE) {
16371
- const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
16791
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path30, 50);
16372
16792
  if (isThrottled)
16373
16793
  return this;
16374
16794
  }
16375
16795
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
16376
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path28) : path28;
16796
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path30) : path30;
16377
16797
  let stats2;
16378
16798
  try {
16379
16799
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -16404,23 +16824,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16404
16824
  * @param timeout duration of time to suppress duplicate actions
16405
16825
  * @returns tracking object or false if action should be suppressed
16406
16826
  */
16407
- _throttle(actionType, path28, timeout) {
16827
+ _throttle(actionType, path30, timeout) {
16408
16828
  if (!this._throttled.has(actionType)) {
16409
16829
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
16410
16830
  }
16411
16831
  const action = this._throttled.get(actionType);
16412
16832
  if (!action)
16413
16833
  throw new Error("invalid throttle");
16414
- const actionPath = action.get(path28);
16834
+ const actionPath = action.get(path30);
16415
16835
  if (actionPath) {
16416
16836
  actionPath.count++;
16417
16837
  return false;
16418
16838
  }
16419
16839
  let timeoutObject;
16420
16840
  const clear = () => {
16421
- const item = action.get(path28);
16841
+ const item = action.get(path30);
16422
16842
  const count = item ? item.count : 0;
16423
- action.delete(path28);
16843
+ action.delete(path30);
16424
16844
  clearTimeout(timeoutObject);
16425
16845
  if (item)
16426
16846
  clearTimeout(item.timeoutObject);
@@ -16428,7 +16848,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16428
16848
  };
16429
16849
  timeoutObject = setTimeout(clear, timeout);
16430
16850
  const thr = { timeoutObject, clear, count: 0 };
16431
- action.set(path28, thr);
16851
+ action.set(path30, thr);
16432
16852
  return thr;
16433
16853
  }
16434
16854
  _incrReadyCount() {
@@ -16442,44 +16862,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16442
16862
  * @param event
16443
16863
  * @param awfEmit Callback to be called when ready for event to be emitted.
16444
16864
  */
16445
- _awaitWriteFinish(path28, threshold, event, awfEmit) {
16865
+ _awaitWriteFinish(path30, threshold, event, awfEmit) {
16446
16866
  const awf = this.options.awaitWriteFinish;
16447
16867
  if (typeof awf !== "object")
16448
16868
  return;
16449
16869
  const pollInterval = awf.pollInterval;
16450
16870
  let timeoutHandler;
16451
- let fullPath = path28;
16452
- if (this.options.cwd && !sp2.isAbsolute(path28)) {
16453
- fullPath = sp2.join(this.options.cwd, path28);
16871
+ let fullPath = path30;
16872
+ if (this.options.cwd && !sp2.isAbsolute(path30)) {
16873
+ fullPath = sp2.join(this.options.cwd, path30);
16454
16874
  }
16455
16875
  const now2 = /* @__PURE__ */ new Date();
16456
16876
  const writes = this._pendingWrites;
16457
16877
  function awaitWriteFinishFn(prevStat) {
16458
16878
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
16459
- if (err || !writes.has(path28)) {
16879
+ if (err || !writes.has(path30)) {
16460
16880
  if (err && err.code !== "ENOENT")
16461
16881
  awfEmit(err);
16462
16882
  return;
16463
16883
  }
16464
16884
  const now3 = Number(/* @__PURE__ */ new Date());
16465
16885
  if (prevStat && curStat.size !== prevStat.size) {
16466
- writes.get(path28).lastChange = now3;
16886
+ writes.get(path30).lastChange = now3;
16467
16887
  }
16468
- const pw = writes.get(path28);
16888
+ const pw = writes.get(path30);
16469
16889
  const df = now3 - pw.lastChange;
16470
16890
  if (df >= threshold) {
16471
- writes.delete(path28);
16891
+ writes.delete(path30);
16472
16892
  awfEmit(void 0, curStat);
16473
16893
  } else {
16474
16894
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
16475
16895
  }
16476
16896
  });
16477
16897
  }
16478
- if (!writes.has(path28)) {
16479
- writes.set(path28, {
16898
+ if (!writes.has(path30)) {
16899
+ writes.set(path30, {
16480
16900
  lastChange: now2,
16481
16901
  cancelWait: () => {
16482
- writes.delete(path28);
16902
+ writes.delete(path30);
16483
16903
  clearTimeout(timeoutHandler);
16484
16904
  return event;
16485
16905
  }
@@ -16490,8 +16910,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16490
16910
  /**
16491
16911
  * Determines whether user has asked to ignore this path.
16492
16912
  */
16493
- _isIgnored(path28, stats) {
16494
- if (this.options.atomic && DOT_RE.test(path28))
16913
+ _isIgnored(path30, stats) {
16914
+ if (this.options.atomic && DOT_RE.test(path30))
16495
16915
  return true;
16496
16916
  if (!this._userIgnored) {
16497
16917
  const { cwd } = this.options;
@@ -16501,17 +16921,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16501
16921
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
16502
16922
  this._userIgnored = anymatch(list, void 0);
16503
16923
  }
16504
- return this._userIgnored(path28, stats);
16924
+ return this._userIgnored(path30, stats);
16505
16925
  }
16506
- _isntIgnored(path28, stat4) {
16507
- return !this._isIgnored(path28, stat4);
16926
+ _isntIgnored(path30, stat5) {
16927
+ return !this._isIgnored(path30, stat5);
16508
16928
  }
16509
16929
  /**
16510
16930
  * Provides a set of common helpers and properties relating to symlink handling.
16511
16931
  * @param path file or directory pattern being watched
16512
16932
  */
16513
- _getWatchHelpers(path28) {
16514
- return new WatchHelper(path28, this.options.followSymlinks, this);
16933
+ _getWatchHelpers(path30) {
16934
+ return new WatchHelper(path30, this.options.followSymlinks, this);
16515
16935
  }
16516
16936
  // Directory helpers
16517
16937
  // -----------------
@@ -16543,63 +16963,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16543
16963
  * @param item base path of item/directory
16544
16964
  */
16545
16965
  _remove(directory, item, isDirectory) {
16546
- const path28 = sp2.join(directory, item);
16547
- const fullPath = sp2.resolve(path28);
16548
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path28) || this._watched.has(fullPath);
16549
- if (!this._throttle("remove", path28, 100))
16966
+ const path30 = sp2.join(directory, item);
16967
+ const fullPath = sp2.resolve(path30);
16968
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path30) || this._watched.has(fullPath);
16969
+ if (!this._throttle("remove", path30, 100))
16550
16970
  return;
16551
16971
  if (!isDirectory && this._watched.size === 1) {
16552
16972
  this.add(directory, item, true);
16553
16973
  }
16554
- const wp = this._getWatchedDir(path28);
16974
+ const wp = this._getWatchedDir(path30);
16555
16975
  const nestedDirectoryChildren = wp.getChildren();
16556
- nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
16976
+ nestedDirectoryChildren.forEach((nested) => this._remove(path30, nested));
16557
16977
  const parent = this._getWatchedDir(directory);
16558
16978
  const wasTracked = parent.has(item);
16559
16979
  parent.remove(item);
16560
16980
  if (this._symlinkPaths.has(fullPath)) {
16561
16981
  this._symlinkPaths.delete(fullPath);
16562
16982
  }
16563
- let relPath = path28;
16983
+ let relPath = path30;
16564
16984
  if (this.options.cwd)
16565
- relPath = sp2.relative(this.options.cwd, path28);
16985
+ relPath = sp2.relative(this.options.cwd, path30);
16566
16986
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
16567
16987
  const event = this._pendingWrites.get(relPath).cancelWait();
16568
16988
  if (event === EVENTS.ADD)
16569
16989
  return;
16570
16990
  }
16571
- this._watched.delete(path28);
16991
+ this._watched.delete(path30);
16572
16992
  this._watched.delete(fullPath);
16573
16993
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
16574
- if (wasTracked && !this._isIgnored(path28))
16575
- this._emit(eventName, path28);
16576
- this._closePath(path28);
16994
+ if (wasTracked && !this._isIgnored(path30))
16995
+ this._emit(eventName, path30);
16996
+ this._closePath(path30);
16577
16997
  }
16578
16998
  /**
16579
16999
  * Closes all watchers for a path
16580
17000
  */
16581
- _closePath(path28) {
16582
- this._closeFile(path28);
16583
- const dir = sp2.dirname(path28);
16584
- this._getWatchedDir(dir).remove(sp2.basename(path28));
17001
+ _closePath(path30) {
17002
+ this._closeFile(path30);
17003
+ const dir = sp2.dirname(path30);
17004
+ this._getWatchedDir(dir).remove(sp2.basename(path30));
16585
17005
  }
16586
17006
  /**
16587
17007
  * Closes only file-specific watchers
16588
17008
  */
16589
- _closeFile(path28) {
16590
- const closers = this._closers.get(path28);
17009
+ _closeFile(path30) {
17010
+ const closers = this._closers.get(path30);
16591
17011
  if (!closers)
16592
17012
  return;
16593
17013
  closers.forEach((closer) => closer());
16594
- this._closers.delete(path28);
17014
+ this._closers.delete(path30);
16595
17015
  }
16596
- _addPathCloser(path28, closer) {
17016
+ _addPathCloser(path30, closer) {
16597
17017
  if (!closer)
16598
17018
  return;
16599
- let list = this._closers.get(path28);
17019
+ let list = this._closers.get(path30);
16600
17020
  if (!list) {
16601
17021
  list = [];
16602
- this._closers.set(path28, list);
17022
+ this._closers.set(path30, list);
16603
17023
  }
16604
17024
  list.push(closer);
16605
17025
  }
@@ -16629,12 +17049,291 @@ function watch(paths, options = {}) {
16629
17049
  var chokidar_default = { watch, FSWatcher };
16630
17050
 
16631
17051
  // src/watcher/file-watcher.ts
17052
+ var path23 = __toESM(require("path"), 1);
17053
+
17054
+ // src/watcher/native-recursive-watcher.ts
17055
+ var import_node_fs3 = require("fs");
16632
17056
  var path21 = __toESM(require("path"), 1);
17057
+ var NativeRecursiveWatcher = class {
17058
+ constructor(root, onChange, options = {}) {
17059
+ this.root = root;
17060
+ this.onChange = onChange;
17061
+ this.watchFactory = options.watchFactory ?? this.defaultWatchFactory;
17062
+ this.onError = options.onError;
17063
+ }
17064
+ root;
17065
+ onChange;
17066
+ watcher = null;
17067
+ listenerToken = 0;
17068
+ watchFactory;
17069
+ onError;
17070
+ start() {
17071
+ if (this.watcher) return;
17072
+ const token = ++this.listenerToken;
17073
+ const listener = (_eventType, filename) => {
17074
+ if (this.watcher === null || this.listenerToken !== token) return;
17075
+ const absolutePath = this.toAbsolutePath(filename);
17076
+ const nextResult = this.onChange(absolutePath);
17077
+ if (nextResult instanceof Promise) {
17078
+ void nextResult.catch((error) => {
17079
+ console.error("[codebase-index] Error handling native watcher event:", error);
17080
+ });
17081
+ }
17082
+ };
17083
+ const watcher = this.watchFactory(this.root, listener, {
17084
+ persistent: true,
17085
+ recursive: true
17086
+ });
17087
+ watcher.on?.("error", (error) => {
17088
+ if (this.watcher === watcher && this.listenerToken === token) {
17089
+ this.onError?.(error);
17090
+ }
17091
+ });
17092
+ this.watcher = watcher;
17093
+ }
17094
+ async stop() {
17095
+ const watcher = this.watcher;
17096
+ this.watcher = null;
17097
+ this.listenerToken += 1;
17098
+ if (!watcher) return;
17099
+ await watcher.close();
17100
+ }
17101
+ toAbsolutePath(filename) {
17102
+ if (filename == null) return null;
17103
+ const normalizedFilename = typeof filename === "string" ? filename : filename.toString();
17104
+ const absolutePath = path21.resolve(this.root, normalizedFilename);
17105
+ const relativePath = path21.relative(this.root, absolutePath);
17106
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path21.sep}`) || path21.isAbsolute(relativePath);
17107
+ return outsideRoot ? null : absolutePath;
17108
+ }
17109
+ defaultWatchFactory = (root, listener, options) => (0, import_node_fs3.watch)(root, options, listener);
17110
+ };
17111
+
17112
+ // src/watcher/snapshot.ts
17113
+ var fsPromises4 = __toESM(require("fs/promises"), 1);
17114
+ var path22 = __toESM(require("path"), 1);
17115
+ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17116
+ const normalizedProjectRoot = path22.resolve(projectRoot);
17117
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17118
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17119
+ const maxDepth = config.indexing?.maxDepth ?? -1;
17120
+ const snapshot = /* @__PURE__ */ new Map();
17121
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17122
+ const includeFile = async (filePath) => {
17123
+ const normalizedPath2 = path22.resolve(filePath);
17124
+ if (!shouldIncludeFile(normalizedPath2, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
17125
+ const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17126
+ if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17127
+ };
17128
+ const walk = async (directoryPath, depth) => {
17129
+ let entries;
17130
+ try {
17131
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
17132
+ } catch (error) {
17133
+ if (isMissingFsError(error)) return;
17134
+ if (isPermissionFsError(error)) {
17135
+ unreadablePrefixes.add(path22.resolve(directoryPath));
17136
+ return;
17137
+ }
17138
+ throw error;
17139
+ }
17140
+ for (const entry of entries) {
17141
+ const fullPath = path22.join(directoryPath, entry.name);
17142
+ const relativePath = path22.relative(normalizedProjectRoot, fullPath);
17143
+ if (entry.isDirectory()) {
17144
+ if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
17145
+ if (ignoreFilter.ignores(relativePath)) continue;
17146
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17147
+ } else if (entry.isFile()) {
17148
+ await includeFile(fullPath);
17149
+ }
17150
+ }
17151
+ };
17152
+ await walk(normalizedProjectRoot, 0);
17153
+ await includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths);
17154
+ return { entries: snapshot, unreadablePrefixes };
17155
+ }
17156
+ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
17157
+ const normalizedProjectRoot = path22.resolve(projectRoot);
17158
+ const normalizedTargetPath = path22.resolve(targetPath);
17159
+ if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
17160
+ return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
17161
+ }
17162
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17163
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17164
+ const maxDepth = config.indexing?.maxDepth ?? -1;
17165
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path22.resolve(configPath)));
17166
+ const snapshot = /* @__PURE__ */ new Map();
17167
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17168
+ const includeFile = async (filePath) => {
17169
+ const normalizedPath2 = path22.resolve(filePath);
17170
+ if (!explicitConfigPaths.has(normalizedPath2) && !shouldIncludeFile(
17171
+ normalizedPath2,
17172
+ normalizedProjectRoot,
17173
+ includePatterns,
17174
+ config.exclude,
17175
+ ignoreFilter
17176
+ )) return;
17177
+ const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17178
+ if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17179
+ };
17180
+ const walk = async (directoryPath, depth) => {
17181
+ let entries;
17182
+ try {
17183
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
17184
+ } catch (error) {
17185
+ if (isMissingFsError(error)) return;
17186
+ if (isPermissionFsError(error)) {
17187
+ unreadablePrefixes.add(path22.resolve(directoryPath));
17188
+ return;
17189
+ }
17190
+ throw error;
17191
+ }
17192
+ for (const entry of entries) {
17193
+ const fullPath = path22.join(directoryPath, entry.name);
17194
+ const relativePath = path22.relative(normalizedProjectRoot, fullPath);
17195
+ if (entry.isDirectory()) {
17196
+ if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
17197
+ if (ignoreFilter.ignores(relativePath)) continue;
17198
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17199
+ } else if (entry.isFile()) {
17200
+ await includeFile(fullPath);
17201
+ }
17202
+ }
17203
+ };
17204
+ const targetStat = await readStatIfFile(normalizedTargetPath, unreadablePrefixes);
17205
+ if (targetStat) await includeFile(normalizedTargetPath);
17206
+ else await walk(normalizedTargetPath, 0);
17207
+ await includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, normalizedTargetPath);
17208
+ return { entries: snapshot, unreadablePrefixes };
17209
+ }
17210
+ function completeFileSnapshot(previous, scan) {
17211
+ const completed = new Map(scan.entries);
17212
+ for (const unreadablePrefix of scan.unreadablePrefixes) {
17213
+ for (const [entryPath, entry] of previous) {
17214
+ if (isWithinPath(unreadablePrefix, entryPath) && !completed.has(entryPath)) completed.set(entryPath, entry);
17215
+ }
17216
+ }
17217
+ return completed;
17218
+ }
17219
+ async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
17220
+ for (const configPath of [...new Set(configPaths.map((value) => path22.resolve(value)))]) {
17221
+ if (snapshot.has(configPath)) continue;
17222
+ const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
17223
+ if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17224
+ }
17225
+ }
17226
+ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, targetPath) {
17227
+ await includeExplicitConfigPaths(
17228
+ snapshot,
17229
+ unreadablePrefixes,
17230
+ configPaths.filter((configPath) => isWithinPath(targetPath, path22.resolve(configPath)))
17231
+ );
17232
+ }
17233
+ function isWithinPath(parentPath, childPath) {
17234
+ const relativePath = path22.relative(parentPath, childPath);
17235
+ return relativePath === "" || !relativePath.startsWith(`..${path22.sep}`) && relativePath !== ".." && !path22.isAbsolute(relativePath);
17236
+ }
17237
+ async function readStatIfFile(filePath, unreadablePrefixes) {
17238
+ try {
17239
+ const stat5 = await fsPromises4.stat(filePath);
17240
+ return stat5.isFile() ? stat5 : null;
17241
+ } catch (error) {
17242
+ if (isMissingFsError(error)) return null;
17243
+ if (isPermissionFsError(error)) {
17244
+ unreadablePrefixes.add(path22.resolve(filePath));
17245
+ return null;
17246
+ }
17247
+ throw error;
17248
+ }
17249
+ }
17250
+ function isMissingFsError(error) {
17251
+ return error instanceof Error && ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
17252
+ }
17253
+ function isPermissionFsError(error) {
17254
+ return error instanceof Error && ["EACCES", "EPERM"].includes(error.code ?? "");
17255
+ }
17256
+ var diffTypeOrder = { add: 0, change: 1, unlink: 2 };
17257
+ function diffFileSnapshots(previous, current, forcedChanges = /* @__PURE__ */ new Set()) {
17258
+ const changes = [];
17259
+ for (const [filePath, previousEntry] of previous) {
17260
+ const currentEntry = current.get(filePath);
17261
+ if (!currentEntry) changes.push({ type: "unlink", path: filePath });
17262
+ else if (forcedChanges.has(filePath) || currentEntry.size !== previousEntry.size || currentEntry.mtimeMs !== previousEntry.mtimeMs) {
17263
+ changes.push({ type: "change", path: filePath });
17264
+ }
17265
+ }
17266
+ for (const [filePath] of current) {
17267
+ if (!previous.has(filePath)) changes.push({ type: "add", path: filePath });
17268
+ }
17269
+ return changes.sort((left, right) => left.path.localeCompare(right.path) || diffTypeOrder[left.type] - diffTypeOrder[right.type]);
17270
+ }
17271
+
17272
+ // src/watcher/snapshot-reconciler.ts
17273
+ var FileSnapshotReconciler = class {
17274
+ constructor(projectRoot, config, configPaths) {
17275
+ this.projectRoot = projectRoot;
17276
+ this.config = config;
17277
+ this.configPaths = configPaths;
17278
+ }
17279
+ projectRoot;
17280
+ config;
17281
+ configPaths;
17282
+ snapshot = null;
17283
+ reconciliationTail = Promise.resolve();
17284
+ async initialize() {
17285
+ this.snapshot = (await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths)).entries;
17286
+ }
17287
+ async reconcile(invalidations = []) {
17288
+ if (this.snapshot === null) {
17289
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
17290
+ }
17291
+ const reconciliation = this.reconciliationTail.then(async () => {
17292
+ const previousSnapshot = this.snapshot;
17293
+ if (previousSnapshot === null) {
17294
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
17295
+ }
17296
+ const normalizedInvalidations = invalidations.map((invalidation) => typeof invalidation === "string" || invalidation === null ? { path: invalidation, forceChange: false } : { path: invalidation.path, forceChange: invalidation.forceChange === true });
17297
+ const scopedPaths = normalizedInvalidations.map((invalidation) => invalidation.path).filter((filePath) => filePath !== null);
17298
+ const scan = scopedPaths.length === 0 || scopedPaths.length !== normalizedInvalidations.length ? await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths) : await this.reconcilePaths(previousSnapshot, scopedPaths);
17299
+ const nextSnapshot = completeFileSnapshot(previousSnapshot, scan);
17300
+ const forcedChanges = new Set(normalizedInvalidations.filter((invalidation) => invalidation.path !== null && invalidation.forceChange).map((invalidation) => invalidation.path));
17301
+ const changes = diffFileSnapshots(previousSnapshot, nextSnapshot, forcedChanges);
17302
+ this.snapshot = nextSnapshot;
17303
+ return changes;
17304
+ });
17305
+ this.reconciliationTail = reconciliation.then(() => void 0, () => void 0);
17306
+ return reconciliation;
17307
+ }
17308
+ async reconcilePaths(previousSnapshot, invalidatedPaths) {
17309
+ const scopes = this.getScopes(invalidatedPaths);
17310
+ const entries = new Map(previousSnapshot);
17311
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17312
+ for (const scope of scopes) {
17313
+ for (const previousPath of entries.keys()) {
17314
+ if (isWithinPath(scope, previousPath)) entries.delete(previousPath);
17315
+ }
17316
+ const scopedScan = await buildFileSnapshotForPathScan(this.projectRoot, this.config, this.configPaths, scope);
17317
+ for (const [filePath, entry] of scopedScan.entries) entries.set(filePath, entry);
17318
+ for (const unreadablePrefix of scopedScan.unreadablePrefixes) unreadablePrefixes.add(unreadablePrefix);
17319
+ }
17320
+ return { entries, unreadablePrefixes };
17321
+ }
17322
+ getScopes(invalidatedPaths) {
17323
+ const uniquePaths = [...new Set(invalidatedPaths)].sort((left, right) => left.length - right.length);
17324
+ return uniquePaths.filter((candidate, index) => !uniquePaths.slice(0, index).some(
17325
+ (ancestor) => isWithinPath(ancestor, candidate)
17326
+ ));
17327
+ }
17328
+ };
17329
+
17330
+ // src/watcher/file-watcher.ts
16633
17331
  var FileWatcher = class {
16634
17332
  watcher = null;
16635
17333
  projectRoot;
16636
17334
  config;
16637
17335
  configPath;
17336
+ backend;
16638
17337
  projectConfigPaths;
16639
17338
  pendingChanges = /* @__PURE__ */ new Map();
16640
17339
  debounceTimer = null;
@@ -16644,44 +17343,74 @@ var FileWatcher = class {
16644
17343
  resolveReady = null;
16645
17344
  pollingFallbackAttempted = false;
16646
17345
  pendingClose = null;
17346
+ startupReadySignals = 1;
17347
+ nativeWatcher = null;
17348
+ nativeReconciler = null;
17349
+ nativeSetupGeneration = 0;
17350
+ nativeStarting = false;
17351
+ nativeInitializing = false;
17352
+ nativeReconcileTimer = null;
17353
+ nativeInvalidatedPaths = /* @__PURE__ */ new Map();
17354
+ configPathStates = /* @__PURE__ */ new Map();
16647
17355
  constructor(projectRoot, config, host, options = {}) {
16648
17356
  this.projectRoot = projectRoot;
16649
17357
  this.config = config;
17358
+ this.backend = options.backend ?? "auto";
16650
17359
  this.configPath = options.configPath;
16651
17360
  this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
16652
17361
  }
16653
17362
  start(handler) {
16654
- if (this.watcher) {
17363
+ if (this.watcher || this.nativeWatcher || this.nativeStarting) {
16655
17364
  return;
16656
17365
  }
16657
17366
  this.onChanges = handler;
16658
17367
  this.pollingFallbackAttempted = false;
16659
17368
  this.resetReady();
17369
+ if (this.shouldUseNativeWatcher()) {
17370
+ if (this.hasExternalConfigWatchTarget()) {
17371
+ this.setStartupReadySignals(2);
17372
+ this.startExternalConfigWatcher();
17373
+ }
17374
+ this.nativeStarting = true;
17375
+ void this.createNativeWatcher();
17376
+ return;
17377
+ }
16660
17378
  this.createWatcher();
16661
17379
  }
16662
17380
  resetReady() {
16663
- this.readyPromise = new Promise((resolve15) => {
16664
- this.resolveReady = resolve15;
17381
+ this.readyPromise = new Promise((resolve17) => {
17382
+ this.resolveReady = resolve17;
16665
17383
  });
17384
+ this.startupReadySignals = 1;
16666
17385
  }
16667
- createWatcher(usePolling = false) {
16668
- const ignoreFilter = createIgnoreFilter(this.projectRoot);
16669
- let watchTargets = this.projectRoot;
16670
- if (this.configPath) {
16671
- watchTargets = [this.projectRoot, this.configPath];
16672
- } else {
16673
- const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
16674
- const relativeConfigPath = path21.relative(this.projectRoot, projectConfigPath);
16675
- return this.isOutsideProjectPath(relativeConfigPath);
16676
- }).map((projectConfigPath) => (0, import_fs14.existsSync)(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path21.dirname(projectConfigPath)));
16677
- const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
16678
- if (uniqueExternalConfigTargets.length > 0) {
16679
- watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
16680
- }
17386
+ setStartupReadySignals(expectedSignals) {
17387
+ if (!this.readyPromise) {
17388
+ return;
17389
+ }
17390
+ this.startupReadySignals = Math.max(0, expectedSignals);
17391
+ }
17392
+ reportStartupReadySignal() {
17393
+ if (!this.readyPromise || !this.resolveReady) {
17394
+ return;
17395
+ }
17396
+ if (this.startupReadySignals <= 0) {
17397
+ return;
16681
17398
  }
17399
+ this.startupReadySignals -= 1;
17400
+ if (this.startupReadySignals !== 0) {
17401
+ return;
17402
+ }
17403
+ this.resolveReady();
17404
+ this.resolveReady = null;
17405
+ }
17406
+ createWatcher(watchTargets, usePolling = false, reportsStartupReady = true) {
17407
+ let reportedStartupReady = false;
17408
+ this.configPathStates = this.getConfigPathStates();
17409
+ const ignoreFilter = createIgnoreFilter(this.projectRoot);
17410
+ const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
16682
17411
  const watcherOptions = {
16683
17412
  ignored: (filePath) => {
16684
- const relativePath = path21.relative(this.projectRoot, filePath);
17413
+ const relativePath = path23.relative(this.projectRoot, filePath);
16685
17414
  if (!relativePath) return false;
16686
17415
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
16687
17416
  return false;
@@ -16689,10 +17418,10 @@ var FileWatcher = class {
16689
17418
  if (this.isOutsideProjectPath(relativePath)) {
16690
17419
  return true;
16691
17420
  }
16692
- if (hasFilteredPathSegment(relativePath, path21.sep)) {
17421
+ if (hasFilteredPathSegment(relativePath, path23.sep)) {
16693
17422
  return true;
16694
17423
  }
16695
- if (isRestrictedDirectory(relativePath, path21.sep)) {
17424
+ if (isRestrictedDirectory(relativePath, path23.sep)) {
16696
17425
  return true;
16697
17426
  }
16698
17427
  if (ignoreFilter.ignores(relativePath)) {
@@ -16725,10 +17454,13 @@ var FileWatcher = class {
16725
17454
  watcher = new FSWatcher(watcherOptions);
16726
17455
  }
16727
17456
  this.watcher = watcher;
16728
- watcher.once("ready", () => {
17457
+ watcher.on("ready", () => {
16729
17458
  if (this.watcher !== watcher) return;
16730
- this.resolveReady?.();
16731
- this.resolveReady = null;
17459
+ this.reconcileConfigPathStates();
17460
+ if (reportsStartupReady) {
17461
+ this.reportStartupReadySignal();
17462
+ reportedStartupReady = true;
17463
+ }
16732
17464
  });
16733
17465
  watcher.on("error", (error) => {
16734
17466
  const err = error instanceof Error ? error : null;
@@ -16742,10 +17474,13 @@ var FileWatcher = class {
16742
17474
  console.error("[codebase-index] Failed to close exhausted file watcher:", closeError);
16743
17475
  });
16744
17476
  if (this.onChanges) {
17477
+ const replacementReportsStartupReady = reportsStartupReady || reportedStartupReady;
16745
17478
  if (!this.resolveReady) {
16746
17479
  this.resetReady();
17480
+ } else if (reportedStartupReady) {
17481
+ this.startupReadySignals += 1;
16747
17482
  }
16748
- this.createWatcher(true);
17483
+ this.createWatcher(resolvedWatchTargets, true, replacementReportsStartupReady);
16749
17484
  } else {
16750
17485
  this.watcher = null;
16751
17486
  }
@@ -16756,13 +17491,166 @@ var FileWatcher = class {
16756
17491
  watcher.on("add", (filePath) => this.handleChange(watcher, "add", filePath));
16757
17492
  watcher.on("change", (filePath) => this.handleChange(watcher, "change", filePath));
16758
17493
  watcher.on("unlink", (filePath) => this.handleChange(watcher, "unlink", filePath));
16759
- watcher.add(watchTargets);
17494
+ watcher.add(resolvedWatchTargets);
17495
+ }
17496
+ shouldUseNativeWatcher() {
17497
+ if (this.backend === "chokidar") {
17498
+ return false;
17499
+ }
17500
+ return true;
17501
+ }
17502
+ getFullChokidarWatchTargets() {
17503
+ if (this.configPath) {
17504
+ return [this.projectRoot, this.configPath];
17505
+ }
17506
+ const externalConfigTargets = this.getExternalConfigWatchTargets();
17507
+ if (externalConfigTargets.length === 0) {
17508
+ return this.projectRoot;
17509
+ }
17510
+ return [this.projectRoot, ...externalConfigTargets];
17511
+ }
17512
+ getExternalConfigWatchTargets() {
17513
+ return [...new Set(
17514
+ this.projectConfigPaths.filter((projectConfigPath) => {
17515
+ const relativeConfigPath = path23.relative(this.projectRoot, projectConfigPath);
17516
+ return this.isOutsideProjectPath(relativeConfigPath);
17517
+ }).map((projectConfigPath) => {
17518
+ if ((0, import_fs14.existsSync)(projectConfigPath)) {
17519
+ return projectConfigPath;
17520
+ }
17521
+ return this.getNearestExistingDirectory(path23.dirname(projectConfigPath));
17522
+ })
17523
+ )];
17524
+ }
17525
+ hasExternalConfigWatchTarget() {
17526
+ return this.getExternalConfigWatchTargets().length > 0;
17527
+ }
17528
+ startExternalConfigWatcher(usePolling = false) {
17529
+ const externalTargets = this.getExternalConfigWatchTargets();
17530
+ if (externalTargets.length === 0) {
17531
+ return;
17532
+ }
17533
+ this.createWatcher(externalTargets, usePolling);
17534
+ }
17535
+ async createNativeWatcher() {
17536
+ const generation = ++this.nativeSetupGeneration;
17537
+ const reconciler = new FileSnapshotReconciler(this.projectRoot, this.config, this.projectConfigPaths);
17538
+ const watcher = new NativeRecursiveWatcher(
17539
+ this.projectRoot,
17540
+ (filePath) => this.scheduleNativeReconciliation(generation, filePath),
17541
+ { onError: (error) => void this.fallbackFromNativeWatcher(generation, error) }
17542
+ );
17543
+ this.nativeReconciler = reconciler;
17544
+ this.nativeWatcher = watcher;
17545
+ this.nativeInitializing = true;
17546
+ try {
17547
+ watcher.start();
17548
+ if (!this.isCurrentNativeSetup(generation)) {
17549
+ await watcher.stop();
17550
+ return;
17551
+ }
17552
+ await reconciler.initialize();
17553
+ if (!this.isCurrentNativeSetup(generation) || this.nativeWatcher !== watcher) {
17554
+ await watcher.stop();
17555
+ return;
17556
+ }
17557
+ this.nativeStarting = false;
17558
+ this.nativeInitializing = false;
17559
+ await this.reconcileNativeWatcherWithPendingInvalidations(generation);
17560
+ this.reportStartupReadySignal();
17561
+ } catch (error) {
17562
+ if (!this.isCurrentNativeSetup(generation)) return;
17563
+ this.nativeInitializing = false;
17564
+ if (this.nativeWatcher) {
17565
+ await this.fallbackFromNativeWatcher(generation, error);
17566
+ return;
17567
+ }
17568
+ this.nativeStarting = false;
17569
+ const externalWatcher = this.watcher;
17570
+ this.watcher = null;
17571
+ this.nativeReconciler = null;
17572
+ await externalWatcher?.close();
17573
+ console.warn("[codebase-index] Native recursive watcher unavailable; using Chokidar fallback.", error);
17574
+ this.setStartupReadySignals(1);
17575
+ this.createWatcher();
17576
+ }
17577
+ }
17578
+ isCurrentNativeSetup(generation) {
17579
+ return this.nativeSetupGeneration === generation && this.onChanges !== null;
17580
+ }
17581
+ scheduleNativeReconciliation(generation, filePath) {
17582
+ if (!this.isCurrentNativeSetup(generation)) return;
17583
+ const requiresFullReconciliation = filePath === path23.join(this.projectRoot, ".gitignore");
17584
+ const invalidatedPath = requiresFullReconciliation ? null : filePath;
17585
+ this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
17586
+ if (this.nativeReconcileTimer) {
17587
+ clearTimeout(this.nativeReconcileTimer);
17588
+ }
17589
+ this.nativeReconcileTimer = setTimeout(() => {
17590
+ this.nativeReconcileTimer = null;
17591
+ void this.reconcileNativeWatcherFromQueue(generation);
17592
+ }, 100);
17593
+ }
17594
+ reconcileNativeWatcherFromQueue(generation) {
17595
+ if (!this.isCurrentNativeSetup(generation) || this.nativeInitializing) return;
17596
+ const invalidatedPaths = this.popNativeInvalidations();
17597
+ if (invalidatedPaths.length === 0) return;
17598
+ void this.reconcileNativeWatcher(generation, invalidatedPaths);
17599
+ }
17600
+ async reconcileNativeWatcher(generation, invalidatedPaths) {
17601
+ if (!this.isCurrentNativeSetup(generation) || !this.nativeReconciler) return;
17602
+ try {
17603
+ const reconciler = this.nativeReconciler;
17604
+ const changes = await reconciler.reconcile(invalidatedPaths);
17605
+ if (!this.isCurrentNativeSetup(generation) || this.nativeReconciler !== reconciler) return;
17606
+ this.recordChanges(changes);
17607
+ } catch (error) {
17608
+ await this.fallbackFromNativeWatcher(generation, error);
17609
+ }
17610
+ }
17611
+ async reconcileNativeWatcherWithPendingInvalidations(generation) {
17612
+ const invalidatedPaths = this.popNativeInvalidations();
17613
+ if (invalidatedPaths.length === 0) return;
17614
+ await this.reconcileNativeWatcher(generation, invalidatedPaths);
17615
+ }
17616
+ popNativeInvalidations() {
17617
+ if (this.nativeInvalidatedPaths.size === 0) return [];
17618
+ const invalidations = [...this.nativeInvalidatedPaths].map(([invalidatedPath, forceChange]) => ({
17619
+ path: invalidatedPath,
17620
+ forceChange
17621
+ }));
17622
+ this.nativeInvalidatedPaths.clear();
17623
+ return invalidations;
17624
+ }
17625
+ async fallbackFromNativeWatcher(generation, error) {
17626
+ if (!this.isCurrentNativeSetup(generation)) return;
17627
+ const watcher = this.nativeWatcher;
17628
+ const externalWatcher = this.watcher;
17629
+ this.nativeWatcher = null;
17630
+ this.watcher = null;
17631
+ this.nativeReconciler = null;
17632
+ this.nativeStarting = false;
17633
+ this.nativeInitializing = false;
17634
+ this.nativeSetupGeneration += 1;
17635
+ if (this.nativeReconcileTimer) {
17636
+ clearTimeout(this.nativeReconcileTimer);
17637
+ this.nativeReconcileTimer = null;
17638
+ }
17639
+ this.nativeInvalidatedPaths.clear();
17640
+ this.setStartupReadySignals(1);
17641
+ console.warn("[codebase-index] Native recursive watcher failed; using Chokidar fallback.", error);
17642
+ await watcher?.stop();
17643
+ await externalWatcher?.close();
17644
+ if (this.onChanges) {
17645
+ this.createWatcher();
17646
+ }
16760
17647
  }
16761
17648
  handleChange(watcher, type, filePath) {
16762
17649
  if (this.watcher !== watcher) {
16763
17650
  return;
16764
17651
  }
16765
17652
  if (this.isProjectConfigPath(filePath)) {
17653
+ this.updateConfigPathState(filePath);
16766
17654
  this.pendingChanges.set(filePath, type);
16767
17655
  this.scheduleFlush();
16768
17656
  return;
@@ -16777,27 +17665,33 @@ var FileWatcher = class {
16777
17665
  )) {
16778
17666
  return;
16779
17667
  }
16780
- this.pendingChanges.set(filePath, type);
17668
+ this.recordChanges([{ path: filePath, type }]);
17669
+ }
17670
+ recordChanges(changes) {
17671
+ if (changes.length === 0) return;
17672
+ for (const change of changes) {
17673
+ this.pendingChanges.set(change.path, change.type);
17674
+ }
16781
17675
  this.scheduleFlush();
16782
17676
  }
16783
17677
  isProjectConfigPath(filePath) {
16784
- const relativePath = path21.relative(this.projectRoot, filePath);
16785
- const normalizedRelativePath = path21.normalize(relativePath);
17678
+ const relativePath = path23.relative(this.projectRoot, filePath);
17679
+ const normalizedRelativePath = path23.normalize(relativePath);
16786
17680
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
16787
17681
  }
16788
17682
  isProjectConfigPathOrAncestor(relativePath) {
16789
- const normalizedRelativePath = path21.normalize(relativePath);
17683
+ const normalizedRelativePath = path23.normalize(relativePath);
16790
17684
  return this.getProjectConfigRelativePaths().some(
16791
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path21.sep}`)
17685
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path23.sep}`)
16792
17686
  );
16793
17687
  }
16794
17688
  isOutsideProjectPath(relativePath) {
16795
- return relativePath === ".." || relativePath.startsWith(`..${path21.sep}`) || path21.isAbsolute(relativePath);
17689
+ return relativePath === ".." || relativePath.startsWith(`..${path23.sep}`) || path23.isAbsolute(relativePath);
16796
17690
  }
16797
17691
  getNearestExistingDirectory(directoryPath) {
16798
17692
  let candidate = directoryPath;
16799
17693
  while (!(0, import_fs14.existsSync)(candidate)) {
16800
- const parent = path21.dirname(candidate);
17694
+ const parent = path23.dirname(candidate);
16801
17695
  if (parent === candidate) break;
16802
17696
  candidate = parent;
16803
17697
  }
@@ -16805,9 +17699,51 @@ var FileWatcher = class {
16805
17699
  }
16806
17700
  getProjectConfigRelativePaths() {
16807
17701
  return this.projectConfigPaths.map(
16808
- (configPath) => path21.normalize(path21.relative(this.projectRoot, configPath))
17702
+ (configPath) => path23.normalize(path23.relative(this.projectRoot, configPath))
16809
17703
  );
16810
17704
  }
17705
+ getConfigPathStates() {
17706
+ const states = /* @__PURE__ */ new Map();
17707
+ for (const configPath of this.projectConfigPaths) {
17708
+ const state = this.getConfigPathState(configPath);
17709
+ if (state) states.set(configPath, state);
17710
+ }
17711
+ return states;
17712
+ }
17713
+ getConfigPathState(configPath) {
17714
+ try {
17715
+ const stats = (0, import_fs14.statSync)(configPath);
17716
+ return stats.isFile() ? { mtimeMs: stats.mtimeMs, size: stats.size } : void 0;
17717
+ } catch (error) {
17718
+ void error;
17719
+ return void 0;
17720
+ }
17721
+ }
17722
+ updateConfigPathState(configPath) {
17723
+ const state = this.getConfigPathState(configPath);
17724
+ if (state) {
17725
+ this.configPathStates.set(configPath, state);
17726
+ } else {
17727
+ this.configPathStates.delete(configPath);
17728
+ }
17729
+ }
17730
+ reconcileConfigPathStates() {
17731
+ const nextStates = this.getConfigPathStates();
17732
+ const changes = [];
17733
+ for (const configPath of this.projectConfigPaths) {
17734
+ const previous = this.configPathStates.get(configPath);
17735
+ const next = nextStates.get(configPath);
17736
+ if (!previous && next) {
17737
+ changes.push({ path: configPath, type: "add" });
17738
+ } else if (previous && !next) {
17739
+ changes.push({ path: configPath, type: "unlink" });
17740
+ } else if (previous && next && (previous.size !== next.size || previous.mtimeMs !== next.mtimeMs)) {
17741
+ changes.push({ path: configPath, type: "change" });
17742
+ }
17743
+ }
17744
+ this.configPathStates = nextStates;
17745
+ this.recordChanges(changes);
17746
+ }
16811
17747
  scheduleFlush() {
16812
17748
  if (this.debounceTimer) {
16813
17749
  clearTimeout(this.debounceTimer);
@@ -16821,7 +17757,7 @@ var FileWatcher = class {
16821
17757
  return;
16822
17758
  }
16823
17759
  const changes = Array.from(this.pendingChanges.entries()).map(
16824
- ([path28, type]) => ({ path: path28, type })
17760
+ ([path30, type]) => ({ path: path30, type })
16825
17761
  );
16826
17762
  this.pendingChanges.clear();
16827
17763
  try {
@@ -16835,20 +17771,31 @@ var FileWatcher = class {
16835
17771
  clearTimeout(this.debounceTimer);
16836
17772
  this.debounceTimer = null;
16837
17773
  }
17774
+ if (this.nativeReconcileTimer) {
17775
+ clearTimeout(this.nativeReconcileTimer);
17776
+ this.nativeReconcileTimer = null;
17777
+ }
17778
+ this.nativeInvalidatedPaths.clear();
16838
17779
  const watcher = this.watcher;
17780
+ const nativeWatcher = this.nativeWatcher;
16839
17781
  const pendingClose = this.pendingClose;
16840
17782
  const resolveReady = this.resolveReady;
16841
17783
  this.watcher = null;
17784
+ this.nativeWatcher = null;
17785
+ this.nativeReconciler = null;
17786
+ this.nativeStarting = false;
17787
+ this.nativeInitializing = false;
17788
+ this.nativeSetupGeneration += 1;
16842
17789
  this.pendingClose = null;
16843
17790
  this.resolveReady = null;
16844
17791
  this.readyPromise = null;
16845
17792
  this.pendingChanges.clear();
16846
17793
  this.onChanges = null;
16847
- await Promise.all([watcher?.close(), pendingClose]);
17794
+ await Promise.all([watcher?.close(), nativeWatcher?.stop(), pendingClose]);
16848
17795
  resolveReady?.();
16849
17796
  }
16850
17797
  isRunning() {
16851
- return this.watcher !== null;
17798
+ return this.watcher !== null || this.nativeWatcher !== null || this.nativeStarting;
16852
17799
  }
16853
17800
  async waitUntilReady() {
16854
17801
  await (this.readyPromise ?? Promise.resolve());
@@ -16856,7 +17803,7 @@ var FileWatcher = class {
16856
17803
  };
16857
17804
 
16858
17805
  // src/watcher/git-head-watcher.ts
16859
- var path22 = __toESM(require("path"), 1);
17806
+ var path24 = __toESM(require("path"), 1);
16860
17807
  var GitHeadWatcher = class {
16861
17808
  watcher = null;
16862
17809
  projectRoot;
@@ -16878,13 +17825,13 @@ var GitHeadWatcher = class {
16878
17825
  this.readyPromise = Promise.resolve();
16879
17826
  return;
16880
17827
  }
16881
- this.readyPromise = new Promise((resolve15) => {
16882
- this.resolveReady = resolve15;
17828
+ this.readyPromise = new Promise((resolve17) => {
17829
+ this.resolveReady = resolve17;
16883
17830
  });
16884
17831
  this.onBranchChange = handler;
16885
17832
  this.currentBranch = getCurrentBranch(this.projectRoot);
16886
17833
  const headPath = getHeadPath(this.projectRoot);
16887
- const refsPath = path22.join(this.projectRoot, ".git", "refs", "heads");
17834
+ const refsPath = path24.join(this.projectRoot, ".git", "refs", "heads");
16888
17835
  this.watcher = chokidar_default.watch([headPath, refsPath], {
16889
17836
  persistent: true,
16890
17837
  ignoreInitial: true,
@@ -17535,13 +18482,19 @@ async function resolveSearchContext(input, operations) {
17535
18482
  (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
17536
18483
  );
17537
18484
  };
17538
- const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
18485
+ const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt, prioritizeSourcePaths) => {
17539
18486
  return recordAttempt(
17540
18487
  "conceptual",
17541
18488
  searchQuery,
17542
18489
  scope,
17543
18490
  relaxedFieldsForAttempt,
17544
- (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
18491
+ (trace) => operations.search(
18492
+ searchQuery,
18493
+ MAX_CONTEXT_RESULT_LIMIT,
18494
+ scope,
18495
+ input.diagnostic ? trace : void 0,
18496
+ { prioritizeSourcePaths }
18497
+ )
17545
18498
  );
17546
18499
  };
17547
18500
  const findSuccessfulAttemptState = (route) => {
@@ -17669,10 +18622,12 @@ Explicit symbol lookup only; conceptual search was not attempted.`
17669
18622
  }
17670
18623
  }
17671
18624
  for (const attempt of conceptualAttemptPlan) {
18625
+ const attemptIntent = analyzeQueryIntent(attempt.queryText);
18626
+ const prioritizeSourcePaths = attemptIntent.primary !== "docs" && attemptIntent.primary !== "test";
17672
18627
  if (inferredSymbol && attempt.queryText === inferredSymbol && attempt.queryText !== query) {
17673
18628
  decisions.fallbackFromOriginalConceptualToInferred = true;
17674
18629
  }
17675
- const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
18630
+ const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed, prioritizeSourcePaths);
17676
18631
  if (results.length > 0) {
17677
18632
  const heading = buildPackHeading("conceptual", decisions);
17678
18633
  const intent = analyzeQueryIntent(attempt.queryText);
@@ -17740,7 +18695,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17740
18695
  const directory = input.directory ?? void 0;
17741
18696
  const tokenBudget = input.tokenBudget ?? void 0;
17742
18697
  if (from && to) {
17743
- const path28 = await getCallGraphPath(
18698
+ const path30 = await getCallGraphPath(
17744
18699
  projectRoot,
17745
18700
  host,
17746
18701
  from,
@@ -17749,25 +18704,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17749
18704
  fromFilePath,
17750
18705
  toFilePath
17751
18706
  );
17752
- const pathText = formatCallGraphPathResult(path28);
17753
- if (path28.path.length > 0) {
18707
+ const pathText = formatCallGraphPathResult(path30);
18708
+ if (path30.path.length > 0) {
17754
18709
  const fitted2 = fitTextToContextBudget(
17755
18710
  pathText,
17756
18711
  tokenBudget
17757
18712
  );
17758
18713
  return {
17759
18714
  text: fitted2.text,
17760
- details: fittedDetails("path", fitted2, path28.path.length)
18715
+ details: fittedDetails("path", fitted2, path30.path.length)
17761
18716
  };
17762
18717
  }
17763
- if (path28.from.status !== "resolved" || path28.to.status !== "resolved") {
18718
+ if (path30.from.status !== "resolved" || path30.to.status !== "resolved") {
17764
18719
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
17765
18720
  return {
17766
18721
  text: fitted2.text,
17767
18722
  details: fittedDetails("path", fitted2, 0)
17768
18723
  };
17769
18724
  }
17770
- const resolvedFrom = path28.from;
18725
+ const resolvedFrom = path30.from;
17771
18726
  const { callers } = await getCallGraphData(projectRoot, host, {
17772
18727
  name: to,
17773
18728
  direction: "callers",
@@ -17809,12 +18764,13 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17809
18764
  directory: scope.directory,
17810
18765
  trace
17811
18766
  }),
17812
- search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
18767
+ search: (queryText, retrievalLimit, scope, trace, searchOptions) => searchCodebase(projectRoot, host, queryText, {
17813
18768
  limit: retrievalLimit,
17814
18769
  fileType: scope.fileType,
17815
18770
  directory: scope.directory,
17816
18771
  metadataOnly: true,
17817
- trace
18772
+ trace,
18773
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
17818
18774
  })
17819
18775
  });
17820
18776
  }
@@ -17926,7 +18882,7 @@ async function executeCallGraph(projectRoot, host, args) {
17926
18882
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
17927
18883
  }
17928
18884
  async function executeCallGraphPath(projectRoot, host, args) {
17929
- const path28 = await getCallGraphPath(
18885
+ const path30 = await getCallGraphPath(
17930
18886
  projectRoot,
17931
18887
  host,
17932
18888
  args.from,
@@ -17935,7 +18891,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
17935
18891
  args.fromFilePath,
17936
18892
  args.toFilePath
17937
18893
  );
17938
- return { text: formatCallGraphPathResult(path28) };
18894
+ return { text: formatCallGraphPathResult(path30) };
17939
18895
  }
17940
18896
  async function executeCodeCommunities(projectRoot, host, args) {
17941
18897
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -17945,11 +18901,11 @@ async function executeCodeCommunities(projectRoot, host, args) {
17945
18901
  // src/adapters/opencode/tools.ts
17946
18902
  var import_fs15 = require("fs");
17947
18903
  var os7 = __toESM(require("os"), 1);
17948
- var path25 = __toESM(require("path"), 1);
18904
+ var path27 = __toESM(require("path"), 1);
17949
18905
 
17950
18906
  // src/tools/visualize/activity.ts
17951
18907
  var import_child_process5 = require("child_process");
17952
- var path23 = __toESM(require("path"), 1);
18908
+ var path25 = __toESM(require("path"), 1);
17953
18909
  function attachRecentActivity(data, projectRoot) {
17954
18910
  const activity = readGitActivity(projectRoot);
17955
18911
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -18111,7 +19067,7 @@ function normalizePath3(filePath) {
18111
19067
  return filePath.replace(/\\/g, "/");
18112
19068
  }
18113
19069
  function toGitRelativePath(projectRoot, filePath) {
18114
- const relativePath = path23.isAbsolute(filePath) ? path23.relative(projectRoot, filePath) : filePath;
19070
+ const relativePath = path25.isAbsolute(filePath) ? path25.relative(projectRoot, filePath) : filePath;
18115
19071
  return normalizePath3(relativePath);
18116
19072
  }
18117
19073
 
@@ -18369,7 +19325,7 @@ render();
18369
19325
  }
18370
19326
 
18371
19327
  // src/tools/visualize/transform.ts
18372
- var path24 = __toESM(require("path"), 1);
19328
+ var path26 = __toESM(require("path"), 1);
18373
19329
 
18374
19330
  // src/tools/visualize/modules.ts
18375
19331
  var MAX_MODULES = 18;
@@ -18502,8 +19458,8 @@ function compactModules(prefixToNodes) {
18502
19458
  function deriveModules(nodes) {
18503
19459
  const initial = /* @__PURE__ */ new Map();
18504
19460
  for (const node of nodes) {
18505
- const relative12 = stripToProjectRelative(node.filePath);
18506
- const prefix = modulePrefixFromRelativePath(relative12);
19461
+ const relative14 = stripToProjectRelative(node.filePath);
19462
+ const prefix = modulePrefixFromRelativePath(relative14);
18507
19463
  if (!initial.has(prefix)) initial.set(prefix, []);
18508
19464
  initial.get(prefix)?.push(node);
18509
19465
  }
@@ -18629,7 +19585,7 @@ function transformForVisualization(symbols, edges, options = {}) {
18629
19585
  filePath: s.filePath,
18630
19586
  kind: s.kind,
18631
19587
  line: s.startLine,
18632
- directory: path24.dirname(s.filePath),
19588
+ directory: path26.dirname(s.filePath),
18633
19589
  moduleId: "",
18634
19590
  moduleLabel: ""
18635
19591
  }));
@@ -18729,7 +19685,8 @@ var codebase_peek = tool({
18729
19685
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
18730
19686
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
18731
19687
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
18732
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19688
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19689
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18733
19690
  },
18734
19691
  async execute(args, context) {
18735
19692
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "peek", args.query, {
@@ -18740,7 +19697,8 @@ var codebase_peek = tool({
18740
19697
  metadataOnly: true,
18741
19698
  blameAuthor: args.blameAuthor,
18742
19699
  blameSha: args.blameSha,
18743
- blameSince: args.blameSince
19700
+ blameSince: args.blameSince,
19701
+ blameUntil: args.blameUntil
18744
19702
  }, (results) => {
18745
19703
  const text = formatCodebasePeek(results);
18746
19704
  return { output: text, text };
@@ -18802,7 +19760,9 @@ var find_similar = tool({
18802
19760
  fileType: z3.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
18803
19761
  directory: z3.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
18804
19762
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
18805
- excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
19763
+ excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)"),
19764
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19765
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18806
19766
  },
18807
19767
  async execute(args, context) {
18808
19768
  const results = await findSimilarCode(context?.worktree, DEFAULT_HOST, args.code, {
@@ -18810,7 +19770,9 @@ var find_similar = tool({
18810
19770
  fileType: args.fileType,
18811
19771
  directory: args.directory,
18812
19772
  chunkType: args.chunkType,
18813
- excludeFile: args.excludeFile
19773
+ excludeFile: args.excludeFile,
19774
+ blameSince: args.blameSince,
19775
+ blameUntil: args.blameUntil
18814
19776
  });
18815
19777
  if (results.length === 0) {
18816
19778
  return "No similar code found. Try a different snippet or run index_codebase first.";
@@ -18829,7 +19791,8 @@ var codebase_search = tool({
18829
19791
  contextLines: z3.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
18830
19792
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
18831
19793
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
18832
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19794
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19795
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18833
19796
  },
18834
19797
  async execute(args, context) {
18835
19798
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "search", args.query, {
@@ -18840,7 +19803,8 @@ var codebase_search = tool({
18840
19803
  contextLines: args.contextLines,
18841
19804
  blameAuthor: args.blameAuthor,
18842
19805
  blameSha: args.blameSha,
18843
- blameSince: args.blameSince
19806
+ blameSince: args.blameSince,
19807
+ blameUntil: args.blameUntil
18844
19808
  }, (results) => {
18845
19809
  const text = results.length === 0 ? "No matching code found. Try a different query or run index_codebase first." : formatSearchResults(results, "score");
18846
19810
  return { output: text, text };
@@ -18949,7 +19913,7 @@ var index_visualize = tool({
18949
19913
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
18950
19914
  }
18951
19915
  const html = generateVisualizationHtml(vizData);
18952
- const outputPath = path25.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
19916
+ const outputPath = path27.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
18953
19917
  (0, import_fs15.writeFileSync)(outputPath, html, "utf-8");
18954
19918
  let result = `Temporal call graph visualization generated: ${outputPath}
18955
19919
 
@@ -19053,10 +20017,16 @@ var PI_TOOL_NAMES = [
19053
20017
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
19054
20018
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
19055
20019
  ];
20020
+ var MCP_TOOL_NAMES = [
20021
+ ...PORTABLE_TOOL_NAMES,
20022
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
20023
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
20024
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE
20025
+ ];
19056
20026
 
19057
20027
  // src/commands/loader.ts
19058
20028
  var import_fs16 = require("fs");
19059
- var path26 = __toESM(require("path"), 1);
20029
+ var path28 = __toESM(require("path"), 1);
19060
20030
  function parseFrontmatter(content) {
19061
20031
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
19062
20032
  const match = content.match(frontmatterRegex);
@@ -19082,7 +20052,7 @@ function loadCommandsFromDirectory(commandsDir) {
19082
20052
  }
19083
20053
  const files = (0, import_fs16.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
19084
20054
  for (const file of files) {
19085
- const filePath = path26.join(commandsDir, file);
20055
+ const filePath = path28.join(commandsDir, file);
19086
20056
  let content;
19087
20057
  try {
19088
20058
  content = (0, import_fs16.readFileSync)(filePath, "utf-8");
@@ -19091,7 +20061,7 @@ function loadCommandsFromDirectory(commandsDir) {
19091
20061
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
19092
20062
  }
19093
20063
  const { frontmatter, body } = parseFrontmatter(content);
19094
- const name = path26.basename(file, ".md");
20064
+ const name = path28.basename(file, ".md");
19095
20065
  const description = frontmatter.description || `Run the ${name} command`;
19096
20066
  commands.set(name, {
19097
20067
  description,
@@ -19426,23 +20396,41 @@ var RoutingHintController = class {
19426
20396
  // src/adapters/opencode.ts
19427
20397
  var import_meta2 = {};
19428
20398
  var activeWatchers = /* @__PURE__ */ new Map();
19429
- function replaceActiveWatcher(projectRoot, nextWatcher) {
19430
- const existing = activeWatchers.get(projectRoot);
19431
- if (existing) {
19432
- existing.stop();
19433
- activeWatchers.delete(projectRoot);
19434
- }
19435
- if (nextWatcher) {
19436
- activeWatchers.set(projectRoot, nextWatcher);
20399
+ var watcherReplacementChains = /* @__PURE__ */ new Map();
20400
+ async function replaceActiveWatcher(projectRoot, createNextWatcher) {
20401
+ const chain = (watcherReplacementChains.get(projectRoot) ?? Promise.resolve()).catch(() => void 0).then(async () => {
20402
+ const existing = activeWatchers.get(projectRoot);
20403
+ if (existing) {
20404
+ try {
20405
+ await existing.stop();
20406
+ } catch (error) {
20407
+ console.error("[codebase-index] Failed to stop replaced watcher:", error);
20408
+ throw error;
20409
+ }
20410
+ if (activeWatchers.get(projectRoot) === existing) {
20411
+ activeWatchers.delete(projectRoot);
20412
+ }
20413
+ }
20414
+ if (createNextWatcher) {
20415
+ activeWatchers.set(projectRoot, createNextWatcher());
20416
+ }
20417
+ });
20418
+ watcherReplacementChains.set(projectRoot, chain);
20419
+ try {
20420
+ await chain;
20421
+ } finally {
20422
+ if (watcherReplacementChains.get(projectRoot) === chain) {
20423
+ watcherReplacementChains.delete(projectRoot);
20424
+ }
19437
20425
  }
19438
20426
  }
19439
20427
  function getCommandsDir() {
19440
20428
  let currentDir = process.cwd();
19441
20429
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
19442
- currentDir = path27.dirname((0, import_url.fileURLToPath)(import_meta2.url));
20430
+ currentDir = path29.dirname((0, import_url.fileURLToPath)(import_meta2.url));
19443
20431
  }
19444
- const packageRoot = path27.basename(currentDir) === "adapters" ? path27.join(currentDir, "..", "..") : path27.join(currentDir, "..");
19445
- return path27.join(packageRoot, "commands");
20432
+ const packageRoot = path29.basename(currentDir) === "adapters" ? path29.join(currentDir, "..", "..") : path29.join(currentDir, "..");
20433
+ return path29.join(packageRoot, "commands");
19446
20434
  }
19447
20435
  function appendRoutingHints(output, hints, preferredRole) {
19448
20436
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -19483,9 +20471,12 @@ var plugin = async ({ directory, worktree }) => {
19483
20471
  startAutoIndex(projectRoot, "opencode", "startup");
19484
20472
  }
19485
20473
  if (config.indexing.watchFiles && isValidProject) {
19486
- replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode"));
20474
+ await replaceActiveWatcher(
20475
+ projectRoot,
20476
+ () => createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode")
20477
+ );
19487
20478
  } else {
19488
- replaceActiveWatcher(projectRoot, null);
20479
+ await replaceActiveWatcher(projectRoot, null);
19489
20480
  }
19490
20481
  return {
19491
20482
  tool: {