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.js CHANGED
@@ -328,7 +328,7 @@ var require_ignore = __commonJS({
328
328
  // path matching.
329
329
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
330
330
  // @returns {TestResult} true if a file is ignored
331
- test(path28, checkUnignored, mode) {
331
+ test(path30, checkUnignored, mode) {
332
332
  let ignored = false;
333
333
  let unignored = false;
334
334
  let matchedRule;
@@ -337,7 +337,7 @@ var require_ignore = __commonJS({
337
337
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
338
338
  return;
339
339
  }
340
- const matched = rule[mode].test(path28);
340
+ const matched = rule[mode].test(path30);
341
341
  if (!matched) {
342
342
  return;
343
343
  }
@@ -358,17 +358,17 @@ var require_ignore = __commonJS({
358
358
  var throwError = (message, Ctor) => {
359
359
  throw new Ctor(message);
360
360
  };
361
- var checkPath = (path28, originalPath, doThrow) => {
362
- if (!isString(path28)) {
361
+ var checkPath = (path30, originalPath, doThrow) => {
362
+ if (!isString(path30)) {
363
363
  return doThrow(
364
364
  `path must be a string, but got \`${originalPath}\``,
365
365
  TypeError
366
366
  );
367
367
  }
368
- if (!path28) {
368
+ if (!path30) {
369
369
  return doThrow(`path must not be empty`, TypeError);
370
370
  }
371
- if (checkPath.isNotRelative(path28)) {
371
+ if (checkPath.isNotRelative(path30)) {
372
372
  const r = "`path.relative()`d";
373
373
  return doThrow(
374
374
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -377,7 +377,7 @@ var require_ignore = __commonJS({
377
377
  }
378
378
  return true;
379
379
  };
380
- var isNotRelative = (path28) => REGEX_TEST_INVALID_PATH.test(path28);
380
+ var isNotRelative = (path30) => REGEX_TEST_INVALID_PATH.test(path30);
381
381
  checkPath.isNotRelative = isNotRelative;
382
382
  checkPath.convert = (p) => p;
383
383
  var Ignore2 = class {
@@ -407,19 +407,19 @@ var require_ignore = __commonJS({
407
407
  }
408
408
  // @returns {TestResult}
409
409
  _test(originalPath, cache, checkUnignored, slices) {
410
- const path28 = originalPath && checkPath.convert(originalPath);
410
+ const path30 = originalPath && checkPath.convert(originalPath);
411
411
  checkPath(
412
- path28,
412
+ path30,
413
413
  originalPath,
414
414
  this._strictPathCheck ? throwError : RETURN_FALSE
415
415
  );
416
- return this._t(path28, cache, checkUnignored, slices);
416
+ return this._t(path30, cache, checkUnignored, slices);
417
417
  }
418
- checkIgnore(path28) {
419
- if (!REGEX_TEST_TRAILING_SLASH.test(path28)) {
420
- return this.test(path28);
418
+ checkIgnore(path30) {
419
+ if (!REGEX_TEST_TRAILING_SLASH.test(path30)) {
420
+ return this.test(path30);
421
421
  }
422
- const slices = path28.split(SLASH2).filter(Boolean);
422
+ const slices = path30.split(SLASH2).filter(Boolean);
423
423
  slices.pop();
424
424
  if (slices.length) {
425
425
  const parent = this._t(
@@ -432,18 +432,18 @@ var require_ignore = __commonJS({
432
432
  return parent;
433
433
  }
434
434
  }
435
- return this._rules.test(path28, false, MODE_CHECK_IGNORE);
435
+ return this._rules.test(path30, false, MODE_CHECK_IGNORE);
436
436
  }
437
- _t(path28, cache, checkUnignored, slices) {
438
- if (path28 in cache) {
439
- return cache[path28];
437
+ _t(path30, cache, checkUnignored, slices) {
438
+ if (path30 in cache) {
439
+ return cache[path30];
440
440
  }
441
441
  if (!slices) {
442
- slices = path28.split(SLASH2).filter(Boolean);
442
+ slices = path30.split(SLASH2).filter(Boolean);
443
443
  }
444
444
  slices.pop();
445
445
  if (!slices.length) {
446
- return cache[path28] = this._rules.test(path28, checkUnignored, MODE_IGNORE);
446
+ return cache[path30] = this._rules.test(path30, checkUnignored, MODE_IGNORE);
447
447
  }
448
448
  const parent = this._t(
449
449
  slices.join(SLASH2) + SLASH2,
@@ -451,29 +451,29 @@ var require_ignore = __commonJS({
451
451
  checkUnignored,
452
452
  slices
453
453
  );
454
- return cache[path28] = parent.ignored ? parent : this._rules.test(path28, checkUnignored, MODE_IGNORE);
454
+ return cache[path30] = parent.ignored ? parent : this._rules.test(path30, checkUnignored, MODE_IGNORE);
455
455
  }
456
- ignores(path28) {
457
- return this._test(path28, this._ignoreCache, false).ignored;
456
+ ignores(path30) {
457
+ return this._test(path30, this._ignoreCache, false).ignored;
458
458
  }
459
459
  createFilter() {
460
- return (path28) => !this.ignores(path28);
460
+ return (path30) => !this.ignores(path30);
461
461
  }
462
462
  filter(paths) {
463
463
  return makeArray(paths).filter(this.createFilter());
464
464
  }
465
465
  // @returns {TestResult}
466
- test(path28) {
467
- return this._test(path28, this._testCache, true);
466
+ test(path30) {
467
+ return this._test(path30, this._testCache, true);
468
468
  }
469
469
  };
470
470
  var factory = (options) => new Ignore2(options);
471
- var isPathValid = (path28) => checkPath(path28 && checkPath.convert(path28), path28, RETURN_FALSE);
471
+ var isPathValid = (path30) => checkPath(path30 && checkPath.convert(path30), path30, RETURN_FALSE);
472
472
  var setupWindows = () => {
473
473
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
474
474
  checkPath.convert = makePosix;
475
475
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
476
- checkPath.isNotRelative = (path28) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path28) || isNotRelative(path28);
476
+ checkPath.isNotRelative = (path30) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path30) || isNotRelative(path30);
477
477
  };
478
478
  if (
479
479
  // Detect `process` so that it can run in browsers.
@@ -651,7 +651,7 @@ var require_eventemitter3 = __commonJS({
651
651
  });
652
652
 
653
653
  // src/adapters/opencode.ts
654
- import * as path27 from "path";
654
+ import * as path29 from "path";
655
655
  import { fileURLToPath as fileURLToPath2 } from "url";
656
656
 
657
657
  // src/config/constants.ts
@@ -712,6 +712,17 @@ var EMBEDDING_MODELS = {
712
712
  maxTokens: 2048,
713
713
  costPer1MTokens: 0.15,
714
714
  taskAble: true
715
+ },
716
+ "gemini-embedding-2": {
717
+ provider: "google",
718
+ model: "gemini-embedding-2",
719
+ // Keep a conservative, testable default embedding dimension. Gemini Embedding 2 supports
720
+ // flexible dimensions via outputDimensionality.
721
+ dimensions: 1536,
722
+ maxTokens: 8192,
723
+ costPer1MTokens: 0.15,
724
+ taskAble: false,
725
+ promptStyle: "embedding-2"
715
726
  }
716
727
  },
717
728
  "openai": {
@@ -745,26 +756,15 @@ var EMBEDDING_MODELS = {
745
756
  maxTokens: 512,
746
757
  costPer1MTokens: 0
747
758
  }
748
- },
749
- "github-copilot": {
750
- "text-embedding-3-small": {
751
- provider: "github-copilot",
752
- model: "text-embedding-3-small",
753
- dimensions: 1536,
754
- maxTokens: 8191,
755
- costPer1MTokens: 0
756
- }
757
759
  }
758
760
  };
759
761
  var DEFAULT_PROVIDER_MODELS = {
760
- "github-copilot": "text-embedding-3-small",
761
762
  "openai": "text-embedding-3-small",
762
763
  "google": "gemini-embedding-001",
763
764
  "ollama": "nomic-embed-text"
764
765
  };
765
766
  var AUTO_DETECT_PROVIDER_ORDER = [
766
767
  "ollama",
767
- "github-copilot",
768
768
  "openai",
769
769
  "google"
770
770
  ];
@@ -790,6 +790,9 @@ function getDefaultIndexingConfig() {
790
790
  maxDepth: 5,
791
791
  maxFilesPerDirectory: 100,
792
792
  fallbackToTextOnMaxChunks: true,
793
+ // Must stay in sync with DEFAULT_LINES_PER_CHUNK in native/src/lib.rs (the napi
794
+ // fallback used when a native caller omits the argument).
795
+ linesPerChunk: 30,
793
796
  gitBlame: { enabled: false }
794
797
  };
795
798
  }
@@ -923,6 +926,7 @@ function parseConfig(raw) {
923
926
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
924
927
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
925
928
  fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
929
+ linesPerChunk: typeof rawIndexing.linesPerChunk === "number" && Number.isFinite(rawIndexing.linesPerChunk) ? Math.min(Math.max(1, Math.floor(rawIndexing.linesPerChunk)), 4294967295) : defaultIndexing.linesPerChunk,
926
930
  gitBlame: {
927
931
  enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
928
932
  }
@@ -965,6 +969,7 @@ function parseConfig(raw) {
965
969
  let embeddingModel;
966
970
  let customProvider;
967
971
  let reranker;
972
+ 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.';
968
973
  if (embeddingProviderValue === "custom") {
969
974
  embeddingProvider = "custom";
970
975
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -1004,6 +1009,8 @@ function parseConfig(raw) {
1004
1009
  } else if (rawEmbeddingModel) {
1005
1010
  embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
1006
1011
  }
1012
+ } else if (embeddingProviderValue === "github-copilot") {
1013
+ throw new Error(githubCopilotDeprecationMessage);
1007
1014
  } else {
1008
1015
  embeddingProvider = "auto";
1009
1016
  }
@@ -1034,10 +1041,21 @@ function parseConfig(raw) {
1034
1041
  timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
1035
1042
  };
1036
1043
  }
1044
+ const rawEmbedding = input.embedding && typeof input.embedding === "object" ? input.embedding : {};
1045
+ const rawEmbeddingBatch = rawEmbedding.batch && typeof rawEmbedding.batch === "object" ? rawEmbedding.batch : null;
1046
+ const embeddingMaxBatchItems = typeof rawEmbeddingBatch?.maxBatchItems === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchItems) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchItems)) : void 0;
1047
+ const embeddingMaxBatchTokens = typeof rawEmbeddingBatch?.maxBatchTokens === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchTokens) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchTokens)) : void 0;
1048
+ const embedding = embeddingMaxBatchItems !== void 0 || embeddingMaxBatchTokens !== void 0 ? {
1049
+ batch: {
1050
+ ...embeddingMaxBatchItems !== void 0 ? { maxBatchItems: embeddingMaxBatchItems } : {},
1051
+ ...embeddingMaxBatchTokens !== void 0 ? { maxBatchTokens: embeddingMaxBatchTokens } : {}
1052
+ }
1053
+ } : {};
1037
1054
  return {
1038
1055
  embeddingProvider,
1039
1056
  embeddingModel,
1040
1057
  customProvider,
1058
+ embedding,
1041
1059
  scope: isValidScope(scopeValue) ? scopeValue : "project",
1042
1060
  include: includeValue ?? DEFAULT_INCLUDE,
1043
1061
  exclude: excludeValue ?? DEFAULT_EXCLUDE,
@@ -1129,11 +1147,11 @@ function resolveGitDir(repoRoot) {
1129
1147
  return null;
1130
1148
  }
1131
1149
  try {
1132
- const stat4 = statSync2(gitPath);
1133
- if (stat4.isDirectory()) {
1150
+ const stat5 = statSync2(gitPath);
1151
+ if (stat5.isDirectory()) {
1134
1152
  return gitPath;
1135
1153
  }
1136
- if (stat4.isFile()) {
1154
+ if (stat5.isFile()) {
1137
1155
  const content = readFileSync2(gitPath, "utf-8").trim();
1138
1156
  const match = content.match(/^gitdir:\s*(.+)$/);
1139
1157
  if (match) {
@@ -2202,7 +2220,7 @@ function analyzeQueryIntent(query) {
2202
2220
  }
2203
2221
  function isTestPath(filePath) {
2204
2222
  const normalized = normalizePath(filePath);
2205
- return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /\.(?:test|spec)\.[^/]+$/u.test(normalized);
2223
+ return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /(?:\.(?:test|spec)|_(?:test|spec))\.[^/]+$/u.test(normalized) || /(?:^|\/)(?:test|spec)_[^/]+\.[^/]+$/u.test(normalized);
2206
2224
  }
2207
2225
  function isFixturePath(filePath) {
2208
2226
  const normalized = normalizePath(filePath);
@@ -2304,6 +2322,10 @@ function scoreCandidate(query, intent, candidate, originalIndex) {
2304
2322
  let boost = 0;
2305
2323
  if (intent.primary === "conceptual") {
2306
2324
  boost += Math.min(0.14, overlap * 0.14);
2325
+ if (intent.preferSourcePaths) {
2326
+ boost += implementationPath ? 0.32 : 0;
2327
+ if (testPath || fixturePath || docsPath) boost -= 0.35;
2328
+ }
2307
2329
  if (generatedOrVendor) boost -= 0.18;
2308
2330
  if (importChunk || weakContainer) boost -= 0.04;
2309
2331
  } else if (intent.primary === "test") {
@@ -2577,8 +2599,8 @@ function formatExactSearchHandoff(results) {
2577
2599
  }
2578
2600
  function formatContextEvidence(result, index) {
2579
2601
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
2580
- const path28 = compactEvidenceValue(result.filePath, 120);
2581
- return `[${index}] ${result.chunkType}${symbol} in ${path28}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2602
+ const path30 = compactEvidenceValue(result.filePath, 120);
2603
+ return `[${index}] ${result.chunkType}${symbol} in ${path30}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2582
2604
  }
2583
2605
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
2584
2606
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -3281,6 +3303,19 @@ function parseOwner(value) {
3281
3303
  if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3282
3304
  if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
3283
3305
  if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
3306
+ if (candidate.recoveryProtocolVersion !== void 0 && candidate.recoveryProtocolVersion !== 1) return null;
3307
+ if (candidate.projectRoot !== void 0 && typeof candidate.projectRoot !== "string") return null;
3308
+ if (candidate.scopedRoots !== void 0) {
3309
+ if (!Array.isArray(candidate.scopedRoots) || candidate.scopedRoots.some((root) => typeof root !== "string")) {
3310
+ return null;
3311
+ }
3312
+ }
3313
+ if (candidate.clearRecovery !== void 0) {
3314
+ const recovery = candidate.clearRecovery;
3315
+ 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") {
3316
+ return null;
3317
+ }
3318
+ }
3284
3319
  return candidate;
3285
3320
  }
3286
3321
  function parseReclaimOwner(value) {
@@ -3521,13 +3556,18 @@ function isTransientIndexLockContention(error) {
3521
3556
  if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
3522
3557
  return error.reason === "active" || error.reason === "reclaiming";
3523
3558
  }
3524
- function acquireIndexLock(indexPath, operation) {
3559
+ function acquireIndexLock(indexPath, operation, recoveryScope) {
3525
3560
  mkdirSync(indexPath, { recursive: true });
3526
3561
  const canonicalIndexPath = realpathSync2.native(indexPath);
3527
3562
  const lockPath = path9.join(canonicalIndexPath, "indexing.lock");
3528
3563
  cleanupDeadPublicationCandidates(canonicalIndexPath);
3529
3564
  for (let attempt = 0; attempt < 6; attempt += 1) {
3530
- const owner = createOwner(operation);
3565
+ const owner = recoveryScope === void 0 ? createOwner(operation) : {
3566
+ ...createOwner(operation),
3567
+ recoveryProtocolVersion: 1,
3568
+ projectRoot: recoveryScope.projectRoot,
3569
+ scopedRoots: recoveryScope.scopedRoots
3570
+ };
3531
3571
  if (publishJsonDirectory(lockPath, owner)) {
3532
3572
  const lease = {
3533
3573
  canonicalIndexPath,
@@ -3592,6 +3632,33 @@ function releaseIndexLock(lease) {
3592
3632
  }
3593
3633
  return true;
3594
3634
  }
3635
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
3636
+ const currentOwner = readDirectoryOwner(lease.lockPath);
3637
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
3638
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
3639
+ }
3640
+ const nextOwner = { ...currentOwner };
3641
+ if (clearRecovery === null) {
3642
+ delete nextOwner.clearRecovery;
3643
+ } else {
3644
+ nextOwner.clearRecovery = clearRecovery;
3645
+ }
3646
+ const ownerPath = path9.join(lease.lockPath, OWNER_FILE_NAME);
3647
+ const temporaryPath = path9.join(
3648
+ lease.lockPath,
3649
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${randomUUID()}`
3650
+ );
3651
+ try {
3652
+ writeFileSync(temporaryPath, JSON.stringify(nextOwner), {
3653
+ encoding: "utf-8",
3654
+ flag: "wx",
3655
+ mode: 384
3656
+ });
3657
+ retryTransientFilesystemOperation(() => renameSync(temporaryPath, ownerPath));
3658
+ } finally {
3659
+ if (existsSync5(temporaryPath)) rmSync(temporaryPath, { force: true });
3660
+ }
3661
+ }
3595
3662
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
3596
3663
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
3597
3664
  temporaryCounter += 1;
@@ -3741,8 +3808,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3741
3808
  if (entry.isDirectory()) {
3742
3809
  subdirs.push({ fullPath, relativePath });
3743
3810
  } else if (entry.isFile()) {
3744
- const stat4 = await fsPromises.stat(fullPath);
3745
- if (stat4.size > maxFileSize) {
3811
+ const stat5 = await fsPromises.stat(fullPath);
3812
+ if (stat5.size > maxFileSize) {
3746
3813
  skipped.push({ path: relativePath, reason: "too_large" });
3747
3814
  continue;
3748
3815
  }
@@ -3760,7 +3827,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3760
3827
  }
3761
3828
  }
3762
3829
  if (matched) {
3763
- filesInDir.push({ path: fullPath, size: stat4.size });
3830
+ filesInDir.push({ path: fullPath, size: stat5.size });
3764
3831
  }
3765
3832
  }
3766
3833
  }
@@ -3817,8 +3884,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3817
3884
  }
3818
3885
  for (const resolvedKbRoot of normalizedRoots) {
3819
3886
  try {
3820
- const stat4 = await fsPromises.stat(resolvedKbRoot);
3821
- if (!stat4.isDirectory()) {
3887
+ const stat5 = await fsPromises.stat(resolvedKbRoot);
3888
+ if (!stat5.isDirectory()) {
3822
3889
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3823
3890
  continue;
3824
3891
  }
@@ -3852,7 +3919,7 @@ function getErrorMessage(error) {
3852
3919
  return error instanceof Error ? error.message : String(error);
3853
3920
  }
3854
3921
  function runCommand(file, args, options) {
3855
- return new Promise((resolve15, reject) => {
3922
+ return new Promise((resolve17, reject) => {
3856
3923
  childProcess.execFile(
3857
3924
  file,
3858
3925
  args,
@@ -3862,7 +3929,7 @@ function runCommand(file, args, options) {
3862
3929
  reject(error);
3863
3930
  return;
3864
3931
  }
3865
- resolve15(stdout);
3932
+ resolve17(stdout);
3866
3933
  }
3867
3934
  );
3868
3935
  });
@@ -4007,10 +4074,10 @@ function safeFailureMessage(error) {
4007
4074
  }
4008
4075
  function cancellableDelay(delayMs, signal) {
4009
4076
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
4010
- return new Promise((resolve15, reject) => {
4077
+ return new Promise((resolve17, reject) => {
4011
4078
  const timer = setTimeout(() => {
4012
4079
  signal.removeEventListener("abort", onAbort);
4013
- resolve15();
4080
+ resolve17();
4014
4081
  }, delayMs);
4015
4082
  timer.unref?.();
4016
4083
  const onAbort = () => {
@@ -4022,15 +4089,15 @@ function cancellableDelay(delayMs, signal) {
4022
4089
  }
4023
4090
  function withTimeout(promise, timeoutMs) {
4024
4091
  if (timeoutMs <= 0) return Promise.resolve(void 0);
4025
- return new Promise((resolve15) => {
4026
- const timer = setTimeout(() => resolve15(void 0), timeoutMs);
4092
+ return new Promise((resolve17) => {
4093
+ const timer = setTimeout(() => resolve17(void 0), timeoutMs);
4027
4094
  timer.unref?.();
4028
4095
  void promise.then((value) => {
4029
4096
  clearTimeout(timer);
4030
- resolve15(value);
4097
+ resolve17(value);
4031
4098
  }, () => {
4032
4099
  clearTimeout(timer);
4033
- resolve15(void 0);
4100
+ resolve17(void 0);
4034
4101
  });
4035
4102
  });
4036
4103
  }
@@ -4412,17 +4479,17 @@ var AutoIndexCoordinator = class {
4412
4479
  }
4413
4480
  }
4414
4481
  waitForBatteryRetry(delayMs) {
4415
- return new Promise((resolve15) => {
4482
+ return new Promise((resolve17) => {
4416
4483
  const timer = setTimeout(() => {
4417
4484
  if (this.batteryRetryTimer === timer) {
4418
4485
  this.batteryRetryTimer = null;
4419
4486
  this.resolveBatteryRetry = null;
4420
4487
  }
4421
- resolve15();
4488
+ resolve17();
4422
4489
  }, delayMs);
4423
4490
  timer.unref?.();
4424
4491
  this.batteryRetryTimer = timer;
4425
- this.resolveBatteryRetry = resolve15;
4492
+ this.resolveBatteryRetry = resolve17;
4426
4493
  });
4427
4494
  }
4428
4495
  cancelBatteryRetry() {
@@ -4430,9 +4497,9 @@ var AutoIndexCoordinator = class {
4430
4497
  clearTimeout(this.batteryRetryTimer);
4431
4498
  this.batteryRetryTimer = null;
4432
4499
  }
4433
- const resolve15 = this.resolveBatteryRetry;
4500
+ const resolve17 = this.resolveBatteryRetry;
4434
4501
  this.resolveBatteryRetry = null;
4435
- resolve15?.();
4502
+ resolve17?.();
4436
4503
  }
4437
4504
  finishBatteryCheck(batteryCheck) {
4438
4505
  if (this.batteryCheck !== batteryCheck) return;
@@ -4637,7 +4704,7 @@ function pTimeout(promise, options) {
4637
4704
  } = options;
4638
4705
  let timer;
4639
4706
  let abortHandler;
4640
- const wrappedPromise = new Promise((resolve15, reject) => {
4707
+ const wrappedPromise = new Promise((resolve17, reject) => {
4641
4708
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
4642
4709
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
4643
4710
  }
@@ -4651,7 +4718,7 @@ function pTimeout(promise, options) {
4651
4718
  };
4652
4719
  signal.addEventListener("abort", abortHandler, { once: true });
4653
4720
  }
4654
- promise.then(resolve15, reject);
4721
+ promise.then(resolve17, reject);
4655
4722
  if (milliseconds === Number.POSITIVE_INFINITY) {
4656
4723
  return;
4657
4724
  }
@@ -4659,7 +4726,7 @@ function pTimeout(promise, options) {
4659
4726
  timer = customTimers.setTimeout.call(void 0, () => {
4660
4727
  if (fallback) {
4661
4728
  try {
4662
- resolve15(fallback());
4729
+ resolve17(fallback());
4663
4730
  } catch (error) {
4664
4731
  reject(error);
4665
4732
  }
@@ -4669,7 +4736,7 @@ function pTimeout(promise, options) {
4669
4736
  promise.cancel();
4670
4737
  }
4671
4738
  if (message === false) {
4672
- resolve15();
4739
+ resolve17();
4673
4740
  } else if (message instanceof Error) {
4674
4741
  reject(message);
4675
4742
  } else {
@@ -5071,7 +5138,7 @@ var PQueue = class extends import_index.default {
5071
5138
  // Assign unique ID if not provided
5072
5139
  id: options.id ?? (this.#idAssigner++).toString()
5073
5140
  };
5074
- return new Promise((resolve15, reject) => {
5141
+ return new Promise((resolve17, reject) => {
5075
5142
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
5076
5143
  let cleanupQueueAbortHandler = () => void 0;
5077
5144
  const run = async () => {
@@ -5111,7 +5178,7 @@ var PQueue = class extends import_index.default {
5111
5178
  })]);
5112
5179
  }
5113
5180
  const result = await operation;
5114
- resolve15(result);
5181
+ resolve17(result);
5115
5182
  this.emit("completed", result);
5116
5183
  } catch (error) {
5117
5184
  reject(error);
@@ -5299,13 +5366,13 @@ var PQueue = class extends import_index.default {
5299
5366
  });
5300
5367
  }
5301
5368
  async #onEvent(event, filter) {
5302
- return new Promise((resolve15) => {
5369
+ return new Promise((resolve17) => {
5303
5370
  const listener = () => {
5304
5371
  if (filter && !filter()) {
5305
5372
  return;
5306
5373
  }
5307
5374
  this.off(event, listener);
5308
- resolve15();
5375
+ resolve17();
5309
5376
  };
5310
5377
  this.on(event, listener);
5311
5378
  });
@@ -5591,7 +5658,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5591
5658
  const finalDelay = Math.min(delayTime, remainingTime);
5592
5659
  options.signal?.throwIfAborted();
5593
5660
  if (finalDelay > 0) {
5594
- await new Promise((resolve15, reject) => {
5661
+ await new Promise((resolve17, reject) => {
5595
5662
  const onAbort = () => {
5596
5663
  clearTimeout(timeoutToken);
5597
5664
  options.signal?.removeEventListener("abort", onAbort);
@@ -5599,7 +5666,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5599
5666
  };
5600
5667
  const timeoutToken = setTimeout(() => {
5601
5668
  options.signal?.removeEventListener("abort", onAbort);
5602
- resolve15();
5669
+ resolve17();
5603
5670
  }, finalDelay);
5604
5671
  if (options.unref) {
5605
5672
  timeoutToken.unref?.();
@@ -5735,8 +5802,6 @@ async function tryDetectProvider() {
5735
5802
  }
5736
5803
  async function getProviderCredentials(provider) {
5737
5804
  switch (provider) {
5738
- case "github-copilot":
5739
- return getGitHubCopilotCredentials();
5740
5805
  case "openai":
5741
5806
  return getOpenAICredentials();
5742
5807
  case "google":
@@ -5747,22 +5812,6 @@ async function getProviderCredentials(provider) {
5747
5812
  return null;
5748
5813
  }
5749
5814
  }
5750
- function getGitHubCopilotCredentials() {
5751
- const authData = loadOpenCodeAuth();
5752
- const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
5753
- if (!copilotAuth || copilotAuth.type !== "oauth") {
5754
- return null;
5755
- }
5756
- const auth = copilotAuth;
5757
- const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
5758
- return {
5759
- provider: "github-copilot",
5760
- baseUrl,
5761
- refreshToken: copilotAuth.refresh,
5762
- accessToken: copilotAuth.access,
5763
- tokenExpires: copilotAuth.expires
5764
- };
5765
- }
5766
5815
  function getOpenAICredentials() {
5767
5816
  const authData = loadOpenCodeAuth();
5768
5817
  const openaiAuth = authData["openai"];
@@ -5888,8 +5937,6 @@ async function tryDetectOllamaProvider() {
5888
5937
  }
5889
5938
  function getProviderDisplayName(provider) {
5890
5939
  switch (provider) {
5891
- case "github-copilot":
5892
- return "GitHub Copilot";
5893
5940
  case "openai":
5894
5941
  return "OpenAI";
5895
5942
  case "google":
@@ -6114,44 +6161,6 @@ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
6114
6161
  }
6115
6162
  };
6116
6163
 
6117
- // src/embeddings/providers/github-copilot.ts
6118
- var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
6119
- constructor(credentials, modelInfo) {
6120
- super(credentials, modelInfo);
6121
- }
6122
- getToken() {
6123
- if (!this.credentials.refreshToken) {
6124
- throw new Error("No OAuth token available for GitHub");
6125
- }
6126
- return this.credentials.refreshToken;
6127
- }
6128
- async embedBatch(texts) {
6129
- const token = this.getToken();
6130
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
6131
- method: "POST",
6132
- headers: {
6133
- Authorization: `Bearer ${token}`,
6134
- "Content-Type": "application/json",
6135
- Accept: "application/vnd.github+json",
6136
- "X-GitHub-Api-Version": "2022-11-28"
6137
- },
6138
- body: JSON.stringify({
6139
- model: `openai/${this.modelInfo.model}`,
6140
- input: texts
6141
- })
6142
- });
6143
- if (!response.ok) {
6144
- const error = (await response.text()).slice(0, 500);
6145
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
6146
- }
6147
- const data = await response.json();
6148
- return {
6149
- embeddings: data.data.map((d) => d.embedding),
6150
- totalTokensUsed: data.usage.total_tokens
6151
- };
6152
- }
6153
- };
6154
-
6155
6164
  // src/embeddings/providers/google.ts
6156
6165
  var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
6157
6166
  static BATCH_SIZE = 20;
@@ -6159,24 +6168,30 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6159
6168
  super(credentials, modelInfo);
6160
6169
  }
6161
6170
  async embedQuery(query) {
6162
- const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6163
- const result = await this.embedWithTaskType([query], taskType);
6171
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6172
+ const texts = [
6173
+ this.modelInfo.model === "gemini-embedding-2" ? `task: code retrieval | query: ${query}` : query
6174
+ ];
6175
+ const result = await this.embedWithTaskType(texts, taskType);
6164
6176
  return {
6165
6177
  embedding: result.embeddings[0],
6166
6178
  tokensUsed: result.totalTokensUsed
6167
6179
  };
6168
6180
  }
6169
6181
  async embedDocument(document) {
6170
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6171
- const result = await this.embedWithTaskType([document], taskType);
6182
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6183
+ const result = await this.embedWithTaskType([
6184
+ this.modelInfo.model === "gemini-embedding-2" ? `title: none | text: ${document}` : document
6185
+ ], taskType);
6172
6186
  return {
6173
6187
  embedding: result.embeddings[0],
6174
6188
  tokensUsed: result.totalTokensUsed
6175
6189
  };
6176
6190
  }
6177
6191
  async embedBatch(texts) {
6178
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6179
- return this.embedWithTaskType(texts, taskType);
6192
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6193
+ const formattedTexts = this.modelInfo.model === "gemini-embedding-2" ? texts.map((text) => `title: none | text: ${text}`) : texts;
6194
+ return this.embedWithTaskType(formattedTexts, taskType);
6180
6195
  }
6181
6196
  async embedWithTaskType(texts, taskType) {
6182
6197
  const batches = [];
@@ -6226,6 +6241,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6226
6241
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
6227
6242
  static MIN_TRUNCATION_CHARS = 512;
6228
6243
  static REQUEST_TIMEOUT_MS = 12e4;
6244
+ // Set when /api/embed returns 404 so subsequent multi-text batches skip the
6245
+ // batched endpoint and go straight to the legacy per-text path (one probe per
6246
+ // old ollama install, not one probe per batch).
6247
+ batchEndpointUnavailable = false;
6229
6248
  constructor(credentials, modelInfo) {
6230
6249
  super(credentials, modelInfo);
6231
6250
  }
@@ -6243,6 +6262,21 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6243
6262
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
6244
6263
  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");
6245
6264
  }
6265
+ // True for a 404 from the newer /api/embed endpoint, i.e. an ollama version that
6266
+ // does not provide it. embedBatch uses this to fall back to the legacy per-text
6267
+ // /api/embeddings path so old ollama installs do not regress.
6268
+ isBatchEndpointUnavailableError(error) {
6269
+ const message = error instanceof Error ? error.message : String(error);
6270
+ return message.includes("Ollama /api/embed not available");
6271
+ }
6272
+ // True for a malformed /api/embed response (wrong vector count or a bad vector).
6273
+ // embedBatch falls back to the per-text path on this so a bad batch response
6274
+ // re-embeds each text cleanly. A text that then fails per-text is not isolated
6275
+ // here; it is isolated on the recovery run, which re-embeds one text per request.
6276
+ isBatchValidationError(error) {
6277
+ const message = error instanceof Error ? error.message : String(error);
6278
+ return message.includes("invalid embedding batch");
6279
+ }
6246
6280
  buildTruncationCandidates(text) {
6247
6281
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
6248
6282
  const candidateLimits = /* @__PURE__ */ new Set();
@@ -6344,7 +6378,74 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6344
6378
  tokensUsed: this.estimateTokens(text)
6345
6379
  };
6346
6380
  }
6347
- async embedBatch(texts) {
6381
+ // Embeds many texts in one POST /api/embed request (input: string[]). Ollama
6382
+ // encodes each input independently, so the model context length applies per input
6383
+ // (the upstream splitter already bounds each input), not over the batch. This
6384
+ // amortizes N HTTP round-trips into one.
6385
+ async embedMany(texts) {
6386
+ const controller = new AbortController();
6387
+ const timeout = setTimeout(
6388
+ () => controller.abort(),
6389
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
6390
+ );
6391
+ let response;
6392
+ try {
6393
+ response = await fetch(`${this.credentials.baseUrl}/api/embed`, {
6394
+ method: "POST",
6395
+ headers: {
6396
+ "Content-Type": "application/json"
6397
+ },
6398
+ body: JSON.stringify({
6399
+ model: this.modelInfo.model,
6400
+ input: texts,
6401
+ truncate: false
6402
+ }),
6403
+ signal: controller.signal
6404
+ });
6405
+ } catch (error) {
6406
+ if (error instanceof Error && error.name === "AbortError") {
6407
+ throw new Error(
6408
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
6409
+ );
6410
+ }
6411
+ throw error;
6412
+ } finally {
6413
+ clearTimeout(timeout);
6414
+ }
6415
+ if (!response.ok) {
6416
+ const error = (await response.text()).slice(0, 500);
6417
+ if (response.status === 404) {
6418
+ throw new Error(`Ollama /api/embed not available: ${response.status} - ${error}`);
6419
+ }
6420
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
6421
+ }
6422
+ let parsed;
6423
+ try {
6424
+ parsed = await response.json();
6425
+ } catch {
6426
+ throw new Error(
6427
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6428
+ );
6429
+ }
6430
+ const data = parsed && typeof parsed === "object" ? parsed : {};
6431
+ if (!Array.isArray(data.embeddings) || data.embeddings.length !== texts.length || data.embeddings.some(
6432
+ (value) => !Array.isArray(value) || value.length !== this.modelInfo.dimensions || value.some((v) => typeof v !== "number" || !Number.isFinite(v))
6433
+ )) {
6434
+ throw new Error(
6435
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6436
+ );
6437
+ }
6438
+ return {
6439
+ embeddings: data.embeddings,
6440
+ totalTokensUsed: texts.reduce((sum, text) => sum + this.estimateTokens(text), 0)
6441
+ };
6442
+ }
6443
+ // Per-text /api/embeddings path shared by the single-text fast path and the
6444
+ // batch fallback. Uses the legacy endpoint one text at a time, so each text gets
6445
+ // its own truncation safety net and a vector validated on its own. A text that
6446
+ // hard-fails per-text throws here and fails the whole request batch; the recovery
6447
+ // run re-embeds one text per request to isolate it.
6448
+ async embedOneByOne(texts) {
6348
6449
  const results = [];
6349
6450
  for (const text of texts) {
6350
6451
  results.push(await this.embedSingleWithFallback(text));
@@ -6354,6 +6455,26 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6354
6455
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
6355
6456
  };
6356
6457
  }
6458
+ async embedBatch(texts) {
6459
+ if (texts.length === 0) {
6460
+ return { embeddings: [], totalTokensUsed: 0 };
6461
+ }
6462
+ if (texts.length === 1 || this.batchEndpointUnavailable) {
6463
+ return this.embedOneByOne(texts);
6464
+ }
6465
+ try {
6466
+ return await this.embedMany(texts);
6467
+ } catch (error) {
6468
+ if (this.isBatchEndpointUnavailableError(error)) {
6469
+ this.batchEndpointUnavailable = true;
6470
+ return this.embedOneByOne(texts);
6471
+ }
6472
+ if (!this.isContextLengthError(error) && !this.isBatchValidationError(error)) {
6473
+ throw error;
6474
+ }
6475
+ return this.embedOneByOne(texts);
6476
+ }
6477
+ }
6357
6478
  };
6358
6479
 
6359
6480
  // src/embeddings/providers/openai.ts
@@ -6388,8 +6509,6 @@ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
6388
6509
  // src/embeddings/provider.ts
6389
6510
  function createEmbeddingProvider(configuredProviderInfo) {
6390
6511
  switch (configuredProviderInfo.provider) {
6391
- case "github-copilot":
6392
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6393
6512
  case "openai":
6394
6513
  return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6395
6514
  case "google":
@@ -6405,85 +6524,6 @@ function createEmbeddingProvider(configuredProviderInfo) {
6405
6524
  }
6406
6525
  }
6407
6526
 
6408
- // src/rerank/index.ts
6409
- function createReranker(config) {
6410
- if (!config.enabled) {
6411
- return new NoOpReranker();
6412
- }
6413
- return new SiliconFlowReranker(config);
6414
- }
6415
- var NoOpReranker = class {
6416
- isAvailable() {
6417
- return false;
6418
- }
6419
- async rerank(_query, documents, _topN) {
6420
- return {
6421
- results: documents.map((_, index) => ({ index, relevanceScore: 0 }))
6422
- };
6423
- }
6424
- };
6425
- var SiliconFlowReranker = class {
6426
- config;
6427
- constructor(config) {
6428
- this.config = config;
6429
- }
6430
- isAvailable() {
6431
- return this.config.enabled && !!this.config.baseUrl && !!this.config.model;
6432
- }
6433
- async rerank(query, documents, topN) {
6434
- if (documents.length === 0) {
6435
- return { results: [] };
6436
- }
6437
- const headers = {
6438
- "Content-Type": "application/json"
6439
- };
6440
- if (this.config.apiKey) {
6441
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
6442
- }
6443
- const baseUrl = this.config.baseUrl;
6444
- if (!baseUrl) {
6445
- throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
6446
- }
6447
- const timeoutMs = this.config.timeoutMs ?? 3e4;
6448
- const controller = new AbortController();
6449
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
6450
- try {
6451
- const response = await fetch(`${baseUrl}/rerank`, {
6452
- method: "POST",
6453
- headers,
6454
- body: JSON.stringify({
6455
- model: this.config.model,
6456
- query,
6457
- documents,
6458
- top_n: topN ?? this.config.topN ?? 20,
6459
- return_documents: false
6460
- }),
6461
- signal: controller.signal
6462
- });
6463
- clearTimeout(timeout);
6464
- if (!response.ok) {
6465
- const errorText = await response.text();
6466
- throw new Error(`Rerank API error: ${response.status} - ${errorText}`);
6467
- }
6468
- const data = await response.json();
6469
- return {
6470
- results: data.results.map((r) => ({
6471
- index: r.index,
6472
- relevanceScore: r.relevance_score,
6473
- document: r.document?.text
6474
- })),
6475
- tokensUsed: data.meta?.tokens?.input_tokens
6476
- };
6477
- } catch (error) {
6478
- clearTimeout(timeout);
6479
- if (error instanceof Error && error.name === "AbortError") {
6480
- throw new Error(`Rerank API request timed out after ${timeoutMs}ms`);
6481
- }
6482
- throw error;
6483
- }
6484
- }
6485
- };
6486
-
6487
6527
  // src/utils/cost.ts
6488
6528
  function estimateChunksFromFiles(files) {
6489
6529
  let totalChunks = 0;
@@ -7219,12 +7259,12 @@ try {
7219
7259
  }
7220
7260
 
7221
7261
  // src/native/parsing.ts
7222
- function parseFileAsText(filePath, content) {
7223
- const result = native.parseFileAsText(filePath, content);
7262
+ function parseFileAsText(filePath, content, linesPerChunk) {
7263
+ const result = native.parseFileAsText(filePath, content, linesPerChunk);
7224
7264
  return result.map(mapChunk);
7225
7265
  }
7226
- function parseFiles(files) {
7227
- const result = native.parseFiles(files);
7266
+ function parseFiles(files, linesPerChunk) {
7267
+ const result = native.parseFiles(files, linesPerChunk);
7228
7268
  return result.map((f) => ({
7229
7269
  path: f.path,
7230
7270
  chunks: f.chunks.map(mapChunk),
@@ -7301,13 +7341,13 @@ var VectorStore = class {
7301
7341
  const metadata = items.map((i) => JSON.stringify(i.metadata));
7302
7342
  this.inner.addBatch(ids, vectors, metadata);
7303
7343
  }
7304
- search(queryVector, limit = 10) {
7344
+ search(queryVector, limit = 10, allowedIds) {
7305
7345
  if (queryVector.length !== this.dimensions) {
7306
7346
  throw new Error(
7307
7347
  `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
7308
7348
  );
7309
7349
  }
7310
- const results = this.inner.search(queryVector, limit);
7350
+ const results = allowedIds === void 0 ? this.inner.search(queryVector, limit) : this.inner.searchFiltered(queryVector, limit, allowedIds);
7311
7351
  return results.map((r) => ({
7312
7352
  id: r.id,
7313
7353
  score: r.score,
@@ -7535,6 +7575,10 @@ var Database = class _Database {
7535
7575
  this.throwIfClosed();
7536
7576
  return this.inner.getBranchChunkIds(branch);
7537
7577
  }
7578
+ getChunkIdsByBlameDate(since, until) {
7579
+ this.throwIfClosed();
7580
+ return this.inner.getChunkIdsByBlameDate(since, until);
7581
+ }
7538
7582
  getBranchDelta(branch, baseBranch) {
7539
7583
  this.throwIfClosed();
7540
7584
  return this.inner.getBranchDelta(branch, baseBranch);
@@ -8043,8 +8087,8 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
8043
8087
  return false;
8044
8088
  }
8045
8089
  function isPathWithinRoot(filePath, rootPath) {
8046
- const relative12 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8047
- return relative12 === "" || !relative12.startsWith(`..${path15.sep}`) && relative12 !== ".." && !path15.isAbsolute(relative12);
8090
+ const relative14 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8091
+ return relative14 === "" || !relative14.startsWith(`..${path15.sep}`) && relative14 !== ".." && !path15.isAbsolute(relative14);
8048
8092
  }
8049
8093
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
8050
8094
  if (await pathExists(worktreePath)) return false;
@@ -8421,11 +8465,11 @@ function normalizeFiles(rawFiles, projectRoot) {
8421
8465
  for (const raw of rawFiles) {
8422
8466
  if (raw.length === 0) continue;
8423
8467
  const absolute = path16.resolve(root, raw);
8424
- const relative12 = path16.relative(root, absolute);
8425
- if (path16.isAbsolute(raw) || relative12 === ".." || relative12.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative12)) {
8468
+ const relative14 = path16.relative(root, absolute);
8469
+ if (path16.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative14)) {
8426
8470
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
8427
8471
  }
8428
- const cleaned = relative12.startsWith(`.${path16.sep}`) ? relative12.slice(2) : relative12;
8472
+ const cleaned = relative14.startsWith(`.${path16.sep}`) ? relative14.slice(2) : relative14;
8429
8473
  if (!seen.has(cleaned)) {
8430
8474
  seen.add(cleaned);
8431
8475
  result.push(cleaned);
@@ -8657,7 +8701,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
8657
8701
  return cached;
8658
8702
  }
8659
8703
  }
8660
- const overfetchLimit = Math.max(options.limit * 4, options.limit);
8704
+ const overfetchFactor = prioritizeSourcePaths ? 12 : 4;
8705
+ const overfetchLimit = Math.max(options.limit * overfetchFactor, options.limit);
8661
8706
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
8662
8707
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
8663
8708
  const rerankPool = fused.slice(0, rerankPoolLimit);
@@ -9422,6 +9467,18 @@ function createFailedBatchWriter(targetPath) {
9422
9467
  temporaryPath
9423
9468
  };
9424
9469
  }
9470
+ function writeFailedBatchRecords(targetPath, records) {
9471
+ const writer = createFailedBatchWriter(targetPath);
9472
+ try {
9473
+ for (const record of records) {
9474
+ writer.write(record);
9475
+ }
9476
+ writer.commit();
9477
+ } catch (error) {
9478
+ writer.cleanup();
9479
+ throw error;
9480
+ }
9481
+ }
9425
9482
  function* readLegacyFailedBatchRecords(filePath, options) {
9426
9483
  const rawData = fs2.readFileSync(filePath, "utf-8");
9427
9484
  const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
@@ -9704,14 +9761,18 @@ function getSafeEmbeddingChunkTokenLimit(provider) {
9704
9761
  const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
9705
9762
  return Math.min(2e3, maxChunkTokens);
9706
9763
  }
9707
- function getDynamicBatchOptions(provider) {
9708
- if (provider.provider === "ollama") {
9709
- return {
9710
- maxBatchTokens: provider.modelInfo.maxTokens,
9711
- maxBatchItems: 1
9712
- };
9764
+ var DEFAULT_OLLAMA_MAX_BATCH_ITEMS = 16;
9765
+ var DEFAULT_OLLAMA_MAX_BATCH_TOKENS = 65536;
9766
+ function getDynamicBatchOptions(provider, embeddingBatch) {
9767
+ if (provider.provider !== "ollama") {
9768
+ return {};
9713
9769
  }
9714
- return {};
9770
+ const base = { maxBatchTokens: DEFAULT_OLLAMA_MAX_BATCH_TOKENS, maxBatchItems: DEFAULT_OLLAMA_MAX_BATCH_ITEMS };
9771
+ return {
9772
+ ...base,
9773
+ ...typeof embeddingBatch?.maxBatchTokens === "number" && Number.isFinite(embeddingBatch.maxBatchTokens) ? { maxBatchTokens: embeddingBatch.maxBatchTokens } : {},
9774
+ ...typeof embeddingBatch?.maxBatchItems === "number" && Number.isFinite(embeddingBatch.maxBatchItems) ? { maxBatchItems: embeddingBatch.maxBatchItems } : {}
9775
+ };
9715
9776
  }
9716
9777
  function isSqliteCorruptionError(error) {
9717
9778
  const message = getErrorMessage4(error).toLowerCase();
@@ -9729,6 +9790,14 @@ function getPendingChunkId(rawChunk) {
9729
9790
  const id = rawChunk.id;
9730
9791
  return typeof id === "string" ? id : null;
9731
9792
  }
9793
+ function parseBlameTimestamp(value, endOfDay) {
9794
+ let timestampMs = Date.parse(value);
9795
+ if (Number.isNaN(timestampMs)) return null;
9796
+ if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
9797
+ timestampMs += 24 * 60 * 60 * 1e3 - 1;
9798
+ }
9799
+ return Math.floor(timestampMs / 1e3);
9800
+ }
9732
9801
  function metadataFromBlame(blame) {
9733
9802
  if (!blame) {
9734
9803
  return {};
@@ -9875,7 +9944,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
9875
9944
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
9876
9945
  return [...promoted, ...remainder];
9877
9946
  }
9878
- function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9947
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source", allowNonSourcePaths = false) {
9879
9948
  if (!prioritizeSourcePaths) {
9880
9949
  return [];
9881
9950
  }
@@ -9895,7 +9964,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9895
9964
  if (!isImplementationChunkType(chunkType)) {
9896
9965
  return false;
9897
9966
  }
9898
- if (!isLikelyImplementationPath2(chunk.filePath)) {
9967
+ if (!allowNonSourcePaths && !isLikelyImplementationPath2(chunk.filePath)) {
9899
9968
  return false;
9900
9969
  }
9901
9970
  const nameLower = (chunk.name ?? "").toLowerCase();
@@ -9959,7 +10028,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9959
10028
  }
9960
10029
  foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
9961
10030
  }
9962
- if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
10031
+ if (foundCoveringChunk || !allowNonSourcePaths && !isLikelyImplementationPath2(symbol.filePath)) {
9963
10032
  continue;
9964
10033
  }
9965
10034
  const symbolName = symbol.name.toLowerCase();
@@ -10013,7 +10082,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
10013
10082
  const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
10014
10083
  if (ranked.length === 0) {
10015
10084
  const implementationFallback = fallbackCandidates.filter(
10016
- (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath2(candidate.metadata.filePath)
10085
+ (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && (allowNonSourcePaths || isLikelyImplementationPath2(candidate.metadata.filePath))
10017
10086
  );
10018
10087
  for (const candidate of implementationFallback) {
10019
10088
  const nameLower = (candidate.metadata.name ?? "").toLowerCase();
@@ -10129,10 +10198,16 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
10129
10198
  return false;
10130
10199
  }
10131
10200
  if (options?.blameSince) {
10132
- const sinceMs = Date.parse(options.blameSince);
10133
- if (Number.isNaN(sinceMs)) return false;
10201
+ const since = parseBlameTimestamp(options.blameSince, false);
10202
+ if (since === null) return false;
10203
+ const committedAt = candidate.metadata.blameCommittedAt;
10204
+ if (committedAt === void 0 || committedAt < since) return false;
10205
+ }
10206
+ if (options?.blameUntil) {
10207
+ const until = parseBlameTimestamp(options.blameUntil, true);
10208
+ if (until === null) return false;
10134
10209
  const committedAt = candidate.metadata.blameCommittedAt;
10135
- if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
10210
+ if (committedAt === void 0 || committedAt > until) return false;
10136
10211
  }
10137
10212
  return true;
10138
10213
  }
@@ -10168,7 +10243,6 @@ var Indexer = class _Indexer {
10168
10243
  database = null;
10169
10244
  provider = null;
10170
10245
  configuredProviderInfo = null;
10171
- reranker = null;
10172
10246
  fileHashCache = /* @__PURE__ */ new Map();
10173
10247
  fileHashCachePath = "";
10174
10248
  failedBatchesPath = "";
@@ -10189,9 +10263,10 @@ var Indexer = class _Indexer {
10189
10263
  writerArtifactFingerprint = null;
10190
10264
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
10191
10265
  fileBatchLimits;
10266
+ checkpointIntervalChunks;
10192
10267
  constructor(projectRoot, config, host, runtimeOptions = {}) {
10193
10268
  this.projectRoot = projectRoot;
10194
- this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10269
+ this.projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10195
10270
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
10196
10271
  this.branchNameOverride = runtimeOptions.branchName;
10197
10272
  this.catalogIdentityOverride = runtimeOptions.catalogIdentity;
@@ -10201,6 +10276,7 @@ var Indexer = class _Indexer {
10201
10276
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
10202
10277
  this.indexPathOverride = runtimeOptions.indexPath;
10203
10278
  this.fileBatchLimits = runtimeOptions.fileBatchLimits;
10279
+ this.checkpointIntervalChunks = runtimeOptions.checkpointIntervalChunks;
10204
10280
  this.config = config;
10205
10281
  this.host = host;
10206
10282
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -10312,6 +10388,9 @@ var Indexer = class _Indexer {
10312
10388
  return path19.resolve(targetPath);
10313
10389
  }
10314
10390
  }
10391
+ getProjectIdentityHash(projectRoot) {
10392
+ return hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10393
+ }
10315
10394
  isProjectOwnedIndexPath() {
10316
10395
  return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
10317
10396
  }
@@ -10328,7 +10407,6 @@ var Indexer = class _Indexer {
10328
10407
  this.database = null;
10329
10408
  this.provider = null;
10330
10409
  this.configuredProviderInfo = null;
10331
- this.reranker = null;
10332
10410
  this.indexCompatibility = null;
10333
10411
  this.initializationMode = "none";
10334
10412
  this.readIssues = [];
@@ -10349,7 +10427,10 @@ var Indexer = class _Indexer {
10349
10427
  }
10350
10428
  async withIndexMutationLease(operation, callback) {
10351
10429
  this.refreshBranchInfo();
10352
- const lease = acquireIndexLock(this.indexPath, operation);
10430
+ const lease = acquireIndexLock(this.indexPath, operation, {
10431
+ projectRoot: this.projectRoot,
10432
+ scopedRoots: this.getScopedRoots()
10433
+ });
10353
10434
  this.indexPath = lease.canonicalIndexPath;
10354
10435
  this.refreshRuntimeArtifactPaths();
10355
10436
  this.activeIndexLease = lease;
@@ -10404,6 +10485,7 @@ var Indexer = class _Indexer {
10404
10485
  }
10405
10486
  loadFileHashCache() {
10406
10487
  if (!existsSync11(this.fileHashCachePath)) {
10488
+ this.fileHashCache = /* @__PURE__ */ new Map();
10407
10489
  return;
10408
10490
  }
10409
10491
  try {
@@ -10443,10 +10525,10 @@ var Indexer = class _Indexer {
10443
10525
  invertedIndex.serialize()
10444
10526
  );
10445
10527
  }
10446
- getScopedRoots() {
10447
- const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
10528
+ getScopedRoots(projectRoot = this.projectRoot) {
10529
+ const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
10448
10530
  for (const kbRoot of this.config.knowledgeBases) {
10449
- roots.add(this.getCanonicalPath(path19.resolve(this.projectRoot, kbRoot)));
10531
+ roots.add(this.getCanonicalPath(path19.resolve(projectRoot, kbRoot)));
10450
10532
  }
10451
10533
  return Array.from(roots);
10452
10534
  }
@@ -10517,14 +10599,17 @@ var Indexer = class _Indexer {
10517
10599
  getLegacyBranchCatalogKey() {
10518
10600
  return this.currentBranch || "default";
10519
10601
  }
10520
- getLegacyMigrationMetadataKey() {
10521
- return `index.globalBranchMigration.${this.projectIdentityHash}`;
10602
+ getLegacyMigrationMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10603
+ return `index.globalBranchMigration.${projectIdentityHash}`;
10522
10604
  }
10523
- getProjectEmbeddingStrategyMetadataKey() {
10524
- return `index.embeddingStrategyVersion.${this.projectIdentityHash}`;
10605
+ getProjectEmbeddingStrategyMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10606
+ return `index.embeddingStrategyVersion.${projectIdentityHash}`;
10525
10607
  }
10526
- getProjectForceReembedMetadataKey() {
10527
- return `index.forceReembed.${this.projectIdentityHash}`;
10608
+ getProjectForceReembedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10609
+ return `index.forceReembed.${projectIdentityHash}`;
10610
+ }
10611
+ getProjectMigrationFinalizedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10612
+ return `index.migrationFinalized.${projectIdentityHash}`;
10528
10613
  }
10529
10614
  getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
10530
10615
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
@@ -10630,7 +10715,7 @@ var Indexer = class _Indexer {
10630
10715
  const legacy = this.getLegacyBranchCatalogKey();
10631
10716
  return primary === legacy ? [primary] : [primary, legacy];
10632
10717
  }
10633
- getProjectLocalScopedOwnershipIds(roots) {
10718
+ getProjectLocalScopedOwnershipIds(roots, projectRoot = this.projectRoot) {
10634
10719
  const chunkIds = /* @__PURE__ */ new Set();
10635
10720
  const symbolIds = /* @__PURE__ */ new Set();
10636
10721
  if (!this.database) {
@@ -10638,10 +10723,10 @@ var Indexer = class _Indexer {
10638
10723
  }
10639
10724
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
10640
10725
  ...Array.from(this.fileHashCache.keys()).filter(
10641
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10726
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10642
10727
  ),
10643
10728
  ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
10644
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10729
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10645
10730
  )
10646
10731
  ]);
10647
10732
  for (const filePath of projectLocalFilePaths) {
@@ -10654,15 +10739,16 @@ var Indexer = class _Indexer {
10654
10739
  }
10655
10740
  return { chunkIds, symbolIds };
10656
10741
  }
10657
- getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
10742
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds, projectRoot = this.projectRoot) {
10658
10743
  if (this.config.scope !== "global") {
10659
10744
  return this.getBranchCatalogCleanupKeys();
10660
10745
  }
10661
10746
  const keys = /* @__PURE__ */ new Set();
10662
10747
  const projectChunkIdSet = new Set(projectChunkIds);
10663
10748
  const projectSymbolIdSet = new Set(projectSymbolIds);
10749
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10664
10750
  for (const branchKey of this.database?.getAllBranches() ?? []) {
10665
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10751
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10666
10752
  keys.add(branchKey);
10667
10753
  continue;
10668
10754
  }
@@ -10672,8 +10758,10 @@ var Indexer = class _Indexer {
10672
10758
  keys.add(branchKey);
10673
10759
  }
10674
10760
  }
10675
- for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10676
- keys.add(branchKey);
10761
+ if (projectRoot === this.projectRoot) {
10762
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10763
+ keys.add(branchKey);
10764
+ }
10677
10765
  }
10678
10766
  return Array.from(keys);
10679
10767
  }
@@ -10681,10 +10769,10 @@ var Indexer = class _Indexer {
10681
10769
  const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
10682
10770
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
10683
10771
  }
10684
- isFileInProjectRoot(filePath) {
10772
+ isFileInProjectRoot(filePath, projectRoot = this.projectRoot) {
10685
10773
  return isPathWithinRoot2(
10686
10774
  this.getCanonicalStoredFilePath(filePath),
10687
- this.getCanonicalPath(this.projectRoot)
10775
+ this.getCanonicalPath(projectRoot)
10688
10776
  );
10689
10777
  }
10690
10778
  clearScopedFileHashCache(roots) {
@@ -10726,12 +10814,12 @@ var Indexer = class _Indexer {
10726
10814
  }
10727
10815
  return false;
10728
10816
  }
10729
- hasForeignScopedBranchData() {
10817
+ hasForeignScopedBranchData(projectRoot = this.projectRoot, roots = this.getScopedRoots(projectRoot)) {
10730
10818
  if (!this.database || this.config.scope !== "global") {
10731
10819
  return false;
10732
10820
  }
10733
- const roots = this.getScopedRoots();
10734
- const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
10821
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10822
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots, projectRoot);
10735
10823
  return this.database.getAllBranches().some(
10736
10824
  (branchKey) => {
10737
10825
  const branchChunkIds = this.database.getBranchChunkIds(branchKey);
@@ -10740,7 +10828,7 @@ var Indexer = class _Indexer {
10740
10828
  if (!hasBranchData) {
10741
10829
  return false;
10742
10830
  }
10743
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10831
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10744
10832
  return false;
10745
10833
  }
10746
10834
  const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
@@ -10749,7 +10837,7 @@ var Indexer = class _Indexer {
10749
10837
  }
10750
10838
  );
10751
10839
  }
10752
- clearSharedIndexProjectData(store, invertedIndex, database, roots) {
10840
+ clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot = this.projectRoot) {
10753
10841
  const allMetadata = store.getAllMetadata();
10754
10842
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
10755
10843
  const filePaths = /* @__PURE__ */ new Set([
@@ -10757,7 +10845,7 @@ var Indexer = class _Indexer {
10757
10845
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
10758
10846
  ]);
10759
10847
  const projectLocalFilePaths = new Set(
10760
- Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath))
10848
+ Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath, projectRoot))
10761
10849
  );
10762
10850
  const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
10763
10851
  for (const filePath of filePaths) {
@@ -10767,7 +10855,7 @@ var Indexer = class _Indexer {
10767
10855
  }
10768
10856
  const removedChunkIdList = Array.from(removedChunkIds);
10769
10857
  const projectLocalChunkIds = new Set(
10770
- scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath)).map(({ key }) => key)
10858
+ scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath, projectRoot)).map(({ key }) => key)
10771
10859
  );
10772
10860
  for (const filePath of projectLocalFilePaths) {
10773
10861
  for (const chunk of database.getChunksByFile(filePath)) {
@@ -10786,7 +10874,8 @@ var Indexer = class _Indexer {
10786
10874
  }
10787
10875
  const branchCleanupKeys = this.getProjectScopedBranchCatalogCleanupKeys(
10788
10876
  Array.from(projectLocalChunkIds),
10789
- Array.from(projectLocalSymbolIds)
10877
+ Array.from(projectLocalSymbolIds),
10878
+ projectRoot
10790
10879
  );
10791
10880
  for (const branchKey of branchCleanupKeys) {
10792
10881
  database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
@@ -10821,29 +10910,96 @@ var Indexer = class _Indexer {
10821
10910
  database.gcOrphanSymbols();
10822
10911
  database.gcOrphanEmbeddings();
10823
10912
  database.gcOrphanChunks();
10824
- store.save();
10825
10913
  this.saveInvertedIndex(invertedIndex);
10914
+ store.save();
10826
10915
  return {
10827
10916
  removedChunkIds: removedChunkIdList,
10828
10917
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
10829
10918
  };
10830
10919
  }
10920
+ getCurrentClearRecoveryState() {
10921
+ if (!this.configuredProviderInfo) {
10922
+ throw new Error("Cannot persist clear recovery state before the embedding provider is initialized");
10923
+ }
10924
+ const compatibility = this.checkCompatibility();
10925
+ const compatibilityDecision = compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible";
10926
+ return {
10927
+ phase: "clearing",
10928
+ embeddingProvider: this.configuredProviderInfo.provider,
10929
+ embeddingModel: this.configuredProviderInfo.modelInfo.model,
10930
+ embeddingDimensions: this.configuredProviderInfo.modelInfo.dimensions,
10931
+ embeddingStrategyVersion: EMBEDDING_STRATEGY_VERSION,
10932
+ compatibilityDecision
10933
+ };
10934
+ }
10935
+ beginClearRecoveryState() {
10936
+ const recovery = this.getCurrentClearRecoveryState();
10937
+ setIndexLockClearRecoveryState(this.requireActiveLease(), recovery);
10938
+ return recovery;
10939
+ }
10940
+ finishClearRecoveryState() {
10941
+ setIndexLockClearRecoveryState(this.requireActiveLease(), null);
10942
+ }
10943
+ matchesCurrentClearRecoveryConfiguration(recovery) {
10944
+ const configuredProviderInfo = this.configuredProviderInfo;
10945
+ return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
10946
+ }
10947
+ hasUnknownLegacyForceIndexClear(owner) {
10948
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && existsSync11(path19.join(this.indexPath, "force-index-phase"));
10949
+ }
10831
10950
  async recoverFromInterruptedIndexingUnlocked(owners) {
10832
10951
  for (const owner of owners) {
10833
10952
  this.logger.warn("Detected interrupted indexing session, recovering...", {
10834
10953
  pid: owner.pid,
10835
10954
  hostname: owner.hostname,
10836
10955
  operation: owner.operation,
10837
- startedAt: owner.startedAt
10956
+ startedAt: owner.startedAt,
10957
+ projectRoot: owner.projectRoot
10838
10958
  });
10839
10959
  }
10840
10960
  if (this.config.scope === "global") {
10841
- if (existsSync11(this.fileHashCachePath)) {
10842
- unlinkSync2(this.fileHashCachePath);
10961
+ const clearScopes = [];
10962
+ for (const owner of owners) {
10963
+ if (this.hasUnknownLegacyForceIndexClear(owner)) {
10964
+ throw new Error(
10965
+ `Cannot automatically recover interrupted force-index ${owner.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
10966
+ );
10967
+ }
10968
+ if (owner.operation === "clear" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1) {
10969
+ throw new Error(
10970
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating recovery state is unknown. The recovery marker was retained for manual inspection.`
10971
+ );
10972
+ }
10973
+ if (owner.clearRecovery === void 0) continue;
10974
+ if (!owner.projectRoot || !owner.scopedRoots || owner.scopedRoots.length === 0) {
10975
+ throw new Error(
10976
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating project scope is unknown. The recovery marker was retained for manual inspection.`
10977
+ );
10978
+ }
10979
+ if (!this.matchesCurrentClearRecoveryConfiguration(owner.clearRecovery)) {
10980
+ throw new Error(
10981
+ `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.`
10982
+ );
10983
+ }
10984
+ clearScopes.push({
10985
+ projectRoot: owner.projectRoot,
10986
+ scopedRoots: owner.scopedRoots,
10987
+ compatibilityDecision: owner.clearRecovery.compatibilityDecision
10988
+ });
10989
+ }
10990
+ if (clearScopes.length > 0) {
10991
+ this.loadFileHashCache();
10992
+ }
10993
+ for (const { projectRoot, scopedRoots, compatibilityDecision } of clearScopes) {
10994
+ this.clearGlobalIndexUnlocked(projectRoot, scopedRoots, compatibilityDecision);
10843
10995
  }
10844
10996
  await this.healthCheckUnlocked();
10997
+ this.logger.info(
10998
+ clearScopes.length > 0 ? "Recovery complete, next index will rebuild all files" : "Recovery complete, next index will resume from the last checkpoint"
10999
+ );
11000
+ return;
10845
11001
  }
10846
- this.logger.info("Recovery complete, next index will re-process all files");
11002
+ this.logger.info("Recovery complete, next index will resume from the last checkpoint");
10847
11003
  }
10848
11004
  *loadSerializedFailedBatches() {
10849
11005
  let warned = false;
@@ -10881,40 +11037,126 @@ var Indexer = class _Indexer {
10881
11037
  state.writer.write(record);
10882
11038
  state.recordsWritten += record.chunks.length;
10883
11039
  }
10884
- finalizeFailedBatchWriteState(state) {
11040
+ finalizeFailedBatchWriteState(state, resolvedChunkIds = /* @__PURE__ */ new Set()) {
10885
11041
  if (state.recordsWritten > 0) {
10886
- state.writer.commit();
10887
- return;
10888
- }
10889
- state.writer.cleanup();
10890
- this.clearFailedBatchState();
10891
- }
10892
- clearFailedBatchState() {
10893
- if (existsSync11(this.failedBatchesPath)) {
10894
- try {
10895
- unlinkSync2(this.failedBatchesPath);
10896
- } catch {
10897
- }
10898
- }
10899
- }
10900
- rewriteFailedBatchState(shouldRetain) {
10901
- const state = this.createFailedBatchWriteState();
10902
- try {
10903
- for (const batch of this.loadSerializedFailedBatches()) {
10904
- const retainedChunks = batch.chunks.filter(shouldRetain);
10905
- if (retainedChunks.length > 0) {
10906
- this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
11042
+ const seenChunkIds = /* @__PURE__ */ new Set();
11043
+ const retained = [];
11044
+ const records = Array.from(readFailedBatchRecords(state.writer.temporaryPath));
11045
+ for (let i = records.length - 1; i >= 0; i--) {
11046
+ const chunks = records[i].chunks.filter((rawChunk) => {
11047
+ const chunkId = getPendingChunkId(rawChunk);
11048
+ if (chunkId !== null) {
11049
+ if (resolvedChunkIds.has(chunkId)) return false;
11050
+ if (seenChunkIds.has(chunkId)) return false;
11051
+ seenChunkIds.add(chunkId);
11052
+ }
11053
+ return true;
11054
+ });
11055
+ if (chunks.length > 0) {
11056
+ retained.unshift({ ...records[i], chunks });
10907
11057
  }
10908
11058
  }
10909
- this.finalizeFailedBatchWriteState(state);
10910
- } catch (error) {
10911
11059
  state.writer.cleanup();
10912
- throw error;
11060
+ if (retained.length > 0) {
11061
+ writeFailedBatchRecords(this.failedBatchesPath, retained);
11062
+ } else {
11063
+ writeFailedBatchRecords(this.failedBatchesPath, []);
11064
+ this.clearFailedBatchState();
11065
+ }
11066
+ return;
11067
+ }
11068
+ state.writer.commit();
11069
+ this.clearFailedBatchState();
11070
+ }
11071
+ getCheckpointIntervalChunks(totalChunks) {
11072
+ return Math.max(
11073
+ this.checkpointIntervalChunks ?? 2e3,
11074
+ Math.floor(totalChunks / 10)
11075
+ );
11076
+ }
11077
+ checkpointIndexRun(database, store, invertedIndex, failedProcessing, resolvedRetryChunkIds, currentFileHashes, committedFilePaths, scopedRoots, configuredProviderInfo) {
11078
+ if (!this.hasProjectForceReembedPending()) {
11079
+ this.saveIndexMetadata(configuredProviderInfo);
11080
+ this.indexCompatibility = { compatible: true };
11081
+ }
11082
+ database.commitWriteTransaction();
11083
+ database.beginWriteTransaction();
11084
+ this.saveInvertedIndex(invertedIndex);
11085
+ store.save();
11086
+ if (failedProcessing.state.recordsWritten > 0 || failedProcessing.latestById.size > 0 || failedProcessing.discardedExistingRecords) {
11087
+ for (const metadata of failedProcessing.latestById.values()) {
11088
+ const alreadyMaterialized = metadata.chunks.some((rawChunk) => {
11089
+ const chunkId = getPendingChunkId(rawChunk);
11090
+ return chunkId !== null && failedProcessing.materializedRetryIds.has(chunkId);
11091
+ });
11092
+ if (alreadyMaterialized) continue;
11093
+ this.writeFailedBatchRecord(failedProcessing.state, {
11094
+ chunks: metadata.chunks,
11095
+ attemptCount: metadata.attemptCount,
11096
+ error: metadata.error,
11097
+ lastAttempt: metadata.lastAttempt
11098
+ });
11099
+ for (const rawChunk of metadata.chunks) {
11100
+ const chunkId = getPendingChunkId(rawChunk);
11101
+ if (chunkId !== null) {
11102
+ failedProcessing.materializedRetryIds.add(chunkId);
11103
+ }
11104
+ }
11105
+ }
11106
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11107
+ failedProcessing.state = this.createFailedBatchWriteState();
11108
+ failedProcessing.discardedExistingRecords = false;
11109
+ for (const record of this.loadSerializedFailedBatches()) {
11110
+ for (const rawChunk of record.chunks) {
11111
+ const chunkId = getPendingChunkId(rawChunk);
11112
+ this.writeFailedBatchRecord(failedProcessing.state, { ...record, chunks: [rawChunk] });
11113
+ if (chunkId !== null) {
11114
+ failedProcessing.materializedRetryIds.add(chunkId);
11115
+ }
11116
+ }
11117
+ }
11118
+ }
11119
+ const partialHashes = /* @__PURE__ */ new Map();
11120
+ for (const filePath of committedFilePaths) {
11121
+ const hash = currentFileHashes.get(filePath);
11122
+ if (hash !== void 0) {
11123
+ partialHashes.set(filePath, hash);
11124
+ }
11125
+ }
11126
+ if (scopedRoots) {
11127
+ this.replaceScopedFileHashCache(partialHashes, scopedRoots);
11128
+ } else {
11129
+ this.fileHashCache = partialHashes;
11130
+ this.saveFileHashCache();
11131
+ }
11132
+ }
11133
+ clearFailedBatchState() {
11134
+ if (existsSync11(this.failedBatchesPath)) {
11135
+ try {
11136
+ unlinkSync2(this.failedBatchesPath);
11137
+ } catch {
11138
+ }
11139
+ }
11140
+ }
11141
+ rewriteFailedBatchState(shouldRetain) {
11142
+ const state = this.createFailedBatchWriteState();
11143
+ try {
11144
+ for (const batch of this.loadSerializedFailedBatches()) {
11145
+ const retainedChunks = batch.chunks.filter(shouldRetain);
11146
+ if (retainedChunks.length > 0) {
11147
+ this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
11148
+ }
11149
+ }
11150
+ this.finalizeFailedBatchWriteState(state);
11151
+ } catch (error) {
11152
+ state.writer.cleanup();
11153
+ throw error;
10913
11154
  }
10914
11155
  }
10915
11156
  prepareFailedBatchProcessing(roots, shouldProcess) {
10916
11157
  const state = this.createFailedBatchWriteState();
10917
11158
  const latestById = /* @__PURE__ */ new Map();
11159
+ let discardedExistingRecords = false;
10918
11160
  try {
10919
11161
  for (const batch of this.loadSerializedFailedBatches()) {
10920
11162
  for (const rawChunk of batch.chunks) {
@@ -10925,10 +11167,12 @@ var Indexer = class _Indexer {
10925
11167
  continue;
10926
11168
  }
10927
11169
  if (!shouldProcess(filePath)) {
11170
+ discardedExistingRecords = true;
10928
11171
  continue;
10929
11172
  }
10930
11173
  const chunkId = getPendingChunkId(rawChunk);
10931
11174
  if (!chunkId) {
11175
+ discardedExistingRecords = true;
10932
11176
  continue;
10933
11177
  }
10934
11178
  const existing = latestById.get(chunkId);
@@ -10936,12 +11180,18 @@ var Indexer = class _Indexer {
10936
11180
  latestById.set(chunkId, {
10937
11181
  attemptCount: batch.attemptCount,
10938
11182
  error: batch.error,
10939
- lastAttempt: batch.lastAttempt
11183
+ lastAttempt: batch.lastAttempt,
11184
+ chunks: [rawChunk]
10940
11185
  });
10941
11186
  }
10942
11187
  }
10943
11188
  }
10944
- return { state, latestById };
11189
+ return {
11190
+ state,
11191
+ latestById,
11192
+ materializedRetryIds: /* @__PURE__ */ new Set(),
11193
+ discardedExistingRecords
11194
+ };
10945
11195
  } catch (error) {
10946
11196
  state.writer.cleanup();
10947
11197
  throw error;
@@ -10977,10 +11227,34 @@ var Indexer = class _Indexer {
10977
11227
  }
10978
11228
  }
10979
11229
  }
11230
+ restoreMissingChunkRows(database, chunks) {
11231
+ const missing = [];
11232
+ for (const chunk of chunks) {
11233
+ if (database.getChunk(chunk.id)) {
11234
+ continue;
11235
+ }
11236
+ missing.push({
11237
+ chunkId: chunk.id,
11238
+ contentHash: chunk.contentHash,
11239
+ filePath: chunk.metadata.filePath,
11240
+ startLine: chunk.metadata.startLine,
11241
+ endLine: chunk.metadata.endLine,
11242
+ nodeType: chunk.metadata.chunkType,
11243
+ name: chunk.metadata.name,
11244
+ language: chunk.metadata.language,
11245
+ blameSha: chunk.metadata.blameSha,
11246
+ blameAuthor: chunk.metadata.blameAuthor,
11247
+ blameAuthorEmail: chunk.metadata.blameAuthorEmail,
11248
+ blameCommittedAt: chunk.metadata.blameCommittedAt,
11249
+ blameSummary: chunk.metadata.blameSummary
11250
+ });
11251
+ }
11252
+ if (missing.length > 0) {
11253
+ database.upsertChunksBatch(missing);
11254
+ }
11255
+ }
10980
11256
  getProviderRateLimits(provider) {
10981
11257
  switch (provider) {
10982
- case "github-copilot":
10983
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
10984
11258
  case "openai":
10985
11259
  return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
10986
11260
  case "google":
@@ -11049,16 +11323,17 @@ var Indexer = class _Indexer {
11049
11323
  const embeddingPartsByChunk = /* @__PURE__ */ new Map();
11050
11324
  const completedVectorsByChunkId = /* @__PURE__ */ new Map();
11051
11325
  const completedChunkIds = /* @__PURE__ */ new Set();
11052
- const requestBatches = createPendingEmbeddingRequestBatches(
11053
- chunksNeedingEmbedding,
11054
- getDynamicBatchOptions(options.configuredProviderInfo)
11055
- );
11326
+ const batchOptions = getDynamicBatchOptions(options.configuredProviderInfo, this.config.embedding?.batch);
11327
+ if (options.forceSingleItemBatches && options.configuredProviderInfo.provider === "ollama") {
11328
+ batchOptions.maxBatchItems = 1;
11329
+ }
11330
+ const requestBatches = createPendingEmbeddingRequestBatches(chunksNeedingEmbedding, batchOptions);
11056
11331
  let fatalError;
11057
11332
  for (const requestBatch of requestBatches) {
11058
11333
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
11059
11334
  const task = options.queue.add(async () => {
11060
11335
  if (options.rateLimitState.backoffMs > 0) {
11061
- await new Promise((resolve15) => setTimeout(resolve15, options.rateLimitState.backoffMs));
11336
+ await new Promise((resolve17) => setTimeout(resolve17, options.rateLimitState.backoffMs));
11062
11337
  }
11063
11338
  try {
11064
11339
  const embeddingResult = await pRetry(
@@ -11615,7 +11890,7 @@ var Indexer = class _Indexer {
11615
11890
  }
11616
11891
  if (!this.configuredProviderInfo) {
11617
11892
  throw new Error(
11618
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11893
+ "No embedding provider available. Configure OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11619
11894
  );
11620
11895
  }
11621
11896
  this.logger.info("Initializing indexer", {
@@ -11625,15 +11900,6 @@ var Indexer = class _Indexer {
11625
11900
  rerankerEnabled: this.config.reranker?.enabled ?? false
11626
11901
  });
11627
11902
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
11628
- if (this.config.reranker?.enabled) {
11629
- this.reranker = createReranker(this.config.reranker);
11630
- if (this.reranker.isAvailable()) {
11631
- this.logger.info("Reranker initialized", {
11632
- model: this.config.reranker.model,
11633
- baseUrl: this.config.reranker.baseUrl
11634
- });
11635
- }
11636
- }
11637
11903
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
11638
11904
  const storePath = path19.join(this.indexPath, "vectors");
11639
11905
  const vectorMetadataPath = `${storePath}.meta.json`;
@@ -11655,7 +11921,20 @@ var Indexer = class _Indexer {
11655
11921
  ]);
11656
11922
  }
11657
11923
  if (recoveredOwners.length > 0 && this.config.scope === "project") {
11658
- await this.resetLocalIndexArtifacts();
11924
+ const unknownLegacyForceIndex = recoveredOwners.find(
11925
+ (owner) => this.hasUnknownLegacyForceIndexClear(owner)
11926
+ );
11927
+ if (unknownLegacyForceIndex) {
11928
+ throw new Error(
11929
+ `Cannot automatically recover interrupted force-index ${unknownLegacyForceIndex.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
11930
+ );
11931
+ }
11932
+ const shouldReset = recoveredOwners.some(
11933
+ (owner) => owner.clearRecovery !== void 0 || owner.operation === "clear" && owner.recoveryProtocolVersion !== 1
11934
+ );
11935
+ if (shouldReset) {
11936
+ await this.resetLocalIndexArtifacts();
11937
+ }
11659
11938
  }
11660
11939
  this.store = new VectorStore(storePath, dimensions);
11661
11940
  if (existsSync11(storePath) || existsSync11(vectorMetadataPath)) {
@@ -12291,7 +12570,17 @@ var Indexer = class _Indexer {
12291
12570
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
12292
12571
  for (const file of files) {
12293
12572
  const storedPath = this.toStoredFilePath(file.path);
12294
- const currentHash = hashFile(file.path);
12573
+ let currentHash;
12574
+ try {
12575
+ currentHash = hashFile(file.path);
12576
+ } catch (error) {
12577
+ stats.skippedFiles.push({ path: this.toCanonicalFilePath(file.path), reason: "unreadable" });
12578
+ this.logger.warn("Skipped unreadable file during indexing", {
12579
+ path: file.path,
12580
+ error: getErrorMessage4(error)
12581
+ });
12582
+ continue;
12583
+ }
12295
12584
  currentFileHashes.set(storedPath, currentHash);
12296
12585
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
12297
12586
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
@@ -12299,7 +12588,8 @@ var Indexer = class _Indexer {
12299
12588
  );
12300
12589
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12301
12590
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
12302
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12591
+ const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
12592
+ if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12303
12593
  unchangedFilePaths.add(storedPath);
12304
12594
  this.logger.recordCacheHit();
12305
12595
  } else {
@@ -12425,6 +12715,9 @@ var Indexer = class _Indexer {
12425
12715
  }
12426
12716
  }
12427
12717
  let processedChangedFiles = 0;
12718
+ let lastCheckpointChunks = 0;
12719
+ const committedFilePaths = new Set(unchangedFilePaths);
12720
+ const resolvedRetryChunkIds = /* @__PURE__ */ new Set();
12428
12721
  for (const descriptorBatch of iterateOrderedFileBatches(
12429
12722
  changedFileDescriptors,
12430
12723
  (descriptor) => descriptor.sourceBytes,
@@ -12438,7 +12731,7 @@ var Indexer = class _Indexer {
12438
12731
  const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
12439
12732
  const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
12440
12733
  const parseStartTime = performance2.now();
12441
- const parsedFiles = parseFiles(loadedFiles);
12734
+ const parsedFiles = parseFiles(loadedFiles, this.config.indexing.linesPerChunk);
12442
12735
  const parseMs = performance2.now() - parseStartTime;
12443
12736
  this.logger.recordFilesParsed(parsedFiles.length);
12444
12737
  this.logger.recordParseDuration(parseMs);
@@ -12461,7 +12754,7 @@ var Indexer = class _Indexer {
12461
12754
  }
12462
12755
  let chunksToProcess = parsed.chunks;
12463
12756
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12464
- chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
12757
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content, this.config.indexing.linesPerChunk);
12465
12758
  }
12466
12759
  chunksToProcess = selectIndexableChunks(
12467
12760
  chunksToProcess,
@@ -12595,6 +12888,10 @@ var Indexer = class _Indexer {
12595
12888
  }
12596
12889
  if (symbolBatch.length > 0) {
12597
12890
  database.upsertSymbolsBatch(symbolBatch);
12891
+ database.addSymbolsToBranchBatch(
12892
+ this.getBranchCatalogKey(),
12893
+ symbolBatch.map((symbol) => symbol.id)
12894
+ );
12598
12895
  }
12599
12896
  if (edgeBatch.length > 0) {
12600
12897
  database.upsertCallEdgesBatch(edgeBatch);
@@ -12630,6 +12927,12 @@ var Indexer = class _Indexer {
12630
12927
  forceReembed: forceScopedReembed,
12631
12928
  reuseCachedEmbeddings: true,
12632
12929
  incrementRepeatedFailures: true,
12930
+ onSucceeded: (succeededChunks) => {
12931
+ database.addChunksToBranchBatch(
12932
+ this.getBranchCatalogKey(),
12933
+ succeededChunks.map((chunk) => chunk.id)
12934
+ );
12935
+ },
12633
12936
  onProgress: (batchProgress) => onProgress?.({
12634
12937
  phase: "embedding",
12635
12938
  filesProcessed: unchangedFilePaths.size + processedChangedFiles,
@@ -12648,6 +12951,27 @@ var Indexer = class _Indexer {
12648
12951
  }
12649
12952
  }
12650
12953
  }
12954
+ for (const descriptor of descriptorBatch) {
12955
+ const existingFileChunks = existingChunksByFile.get(descriptor.storedPath);
12956
+ if (!existingFileChunks || existingFileChunks.size === 0) {
12957
+ committedFilePaths.add(descriptor.storedPath);
12958
+ }
12959
+ }
12960
+ const checkpointInterval = this.getCheckpointIntervalChunks(stats.totalChunks);
12961
+ if (stats.totalChunks - lastCheckpointChunks >= checkpointInterval) {
12962
+ lastCheckpointChunks = stats.totalChunks;
12963
+ this.checkpointIndexRun(
12964
+ database,
12965
+ store,
12966
+ invertedIndex,
12967
+ failedProcessing,
12968
+ resolvedRetryChunkIds,
12969
+ currentFileHashes,
12970
+ committedFilePaths,
12971
+ scopedRoots,
12972
+ configuredProviderInfo
12973
+ );
12974
+ }
12651
12975
  }
12652
12976
  const retryableFailedChunks = this.iterateLatestFailedChunks(
12653
12977
  failedProcessing.latestById,
@@ -12668,6 +12992,7 @@ var Indexer = class _Indexer {
12668
12992
  retryableChunksWithExistingData.add(chunk.id);
12669
12993
  }
12670
12994
  }
12995
+ this.restoreMissingChunkRows(database, pendingChunks);
12671
12996
  stats.totalChunks += pendingChunks.length;
12672
12997
  onProgress?.({
12673
12998
  phase: "embedding",
@@ -12690,6 +13015,17 @@ var Indexer = class _Indexer {
12690
13015
  forceReembed: forceScopedReembed,
12691
13016
  reuseCachedEmbeddings: true,
12692
13017
  incrementRepeatedFailures: true,
13018
+ forceSingleItemBatches: true,
13019
+ onSucceeded: (succeededChunks) => {
13020
+ database.addChunksToBranchBatch(
13021
+ this.getBranchCatalogKey(),
13022
+ succeededChunks.map((chunk) => chunk.id)
13023
+ );
13024
+ for (const chunk of succeededChunks) {
13025
+ failedProcessing.latestById.delete(chunk.id);
13026
+ resolvedRetryChunkIds.add(chunk.id);
13027
+ }
13028
+ },
12693
13029
  onProgress: (batchProgress) => onProgress?.({
12694
13030
  phase: "embedding",
12695
13031
  filesProcessed: files.length,
@@ -12707,6 +13043,20 @@ var Indexer = class _Indexer {
12707
13043
  failedForcedChunkIds.add(chunkId);
12708
13044
  }
12709
13045
  }
13046
+ if (stats.totalChunks - lastCheckpointChunks >= this.getCheckpointIntervalChunks(stats.totalChunks)) {
13047
+ lastCheckpointChunks = stats.totalChunks;
13048
+ this.checkpointIndexRun(
13049
+ database,
13050
+ store,
13051
+ invertedIndex,
13052
+ failedProcessing,
13053
+ resolvedRetryChunkIds,
13054
+ currentFileHashes,
13055
+ committedFilePaths,
13056
+ scopedRoots,
13057
+ configuredProviderInfo
13058
+ );
13059
+ }
12710
13060
  }
12711
13061
  const removedChunkIds = [];
12712
13062
  for (const [chunkId] of existingChunks) {
@@ -12743,13 +13093,6 @@ var Indexer = class _Indexer {
12743
13093
  if (removedStoredChunks) {
12744
13094
  this.saveInvertedIndex(invertedIndex);
12745
13095
  }
12746
- if (scopedRoots) {
12747
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12748
- } else {
12749
- this.fileHashCache = currentFileHashes;
12750
- this.saveFileHashCache();
12751
- }
12752
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12753
13096
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12754
13097
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12755
13098
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12758,6 +13101,13 @@ var Indexer = class _Indexer {
12758
13101
  this.indexCompatibility = { compatible: true };
12759
13102
  database.commitWriteTransaction();
12760
13103
  writeTransactionActive = false;
13104
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13105
+ if (scopedRoots) {
13106
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13107
+ } else {
13108
+ this.fileHashCache = currentFileHashes;
13109
+ this.saveFileHashCache();
13110
+ }
12761
13111
  stats.durationMs = Date.now() - startTime;
12762
13112
  onProgress?.({
12763
13113
  phase: "complete",
@@ -12781,13 +13131,6 @@ var Indexer = class _Indexer {
12781
13131
  );
12782
13132
  store.save();
12783
13133
  this.saveInvertedIndex(invertedIndex);
12784
- if (scopedRoots) {
12785
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12786
- } else {
12787
- this.fileHashCache = currentFileHashes;
12788
- this.saveFileHashCache();
12789
- }
12790
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12791
13134
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12792
13135
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12793
13136
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12796,6 +13139,13 @@ var Indexer = class _Indexer {
12796
13139
  this.indexCompatibility = { compatible: true };
12797
13140
  database.commitWriteTransaction();
12798
13141
  writeTransactionActive = false;
13142
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13143
+ if (scopedRoots) {
13144
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13145
+ } else {
13146
+ this.fileHashCache = currentFileHashes;
13147
+ this.saveFileHashCache();
13148
+ }
12799
13149
  stats.durationMs = Date.now() - startTime;
12800
13150
  onProgress?.({
12801
13151
  phase: "complete",
@@ -12830,15 +13180,15 @@ var Indexer = class _Indexer {
12830
13180
  );
12831
13181
  store.save();
12832
13182
  this.saveInvertedIndex(invertedIndex);
13183
+ database.commitWriteTransaction();
13184
+ writeTransactionActive = false;
13185
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
12833
13186
  if (scopedRoots) {
12834
13187
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12835
13188
  } else {
12836
13189
  this.fileHashCache = currentFileHashes;
12837
13190
  this.saveFileHashCache();
12838
13191
  }
12839
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12840
- database.commitWriteTransaction();
12841
- writeTransactionActive = false;
12842
13192
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
12843
13193
  const gcReset = await this.maybeRunOrphanGc();
12844
13194
  if (gcReset) {
@@ -12862,6 +13212,9 @@ var Indexer = class _Indexer {
12862
13212
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
12863
13213
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12864
13214
  }
13215
+ if (forceScopedReembed) {
13216
+ database.setMetadata(this.getProjectMigrationFinalizedMetadataKey(), "true");
13217
+ }
12865
13218
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12866
13219
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12867
13220
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12972,26 +13325,41 @@ var Indexer = class _Indexer {
12972
13325
  shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
12973
13326
  };
12974
13327
  }
12975
- searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
13328
+ searchCandidatesWithAllowedIds(initialLimit, totalCount, allowedChunkIds, shouldPrefilter, search, getChunkId) {
12976
13329
  const normalizedLimit = Math.max(0, Math.floor(initialLimit));
12977
13330
  if (normalizedLimit === 0) return [];
12978
- if (!shouldPrefilterByBranch || !branchChunkIds) {
13331
+ if (!shouldPrefilter || !allowedChunkIds) {
12979
13332
  return search(normalizedLimit);
12980
13333
  }
12981
- const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
13334
+ const targetCount = Math.min(normalizedLimit, allowedChunkIds.size);
12982
13335
  if (targetCount === 0 || totalCount === 0) return [];
12983
13336
  let requestedLimit = Math.min(normalizedLimit, totalCount);
12984
13337
  while (true) {
12985
13338
  const results = search(requestedLimit);
12986
- const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
12987
- if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
12988
- return branchResults;
13339
+ const allowedResults = results.filter((candidate) => allowedChunkIds.has(getChunkId(candidate)));
13340
+ if (allowedResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
13341
+ return allowedResults;
12989
13342
  }
12990
13343
  const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
12991
- if (nextLimit === requestedLimit) return branchResults;
13344
+ if (nextLimit === requestedLimit) return allowedResults;
12992
13345
  requestedLimit = nextLimit;
12993
13346
  }
12994
13347
  }
13348
+ getTemporalChunkIds(database, options) {
13349
+ if (!options?.blameSince && !options?.blameUntil) return null;
13350
+ const since = options.blameSince ? parseBlameTimestamp(options.blameSince, false) : void 0;
13351
+ const until = options.blameUntil ? parseBlameTimestamp(options.blameUntil, true) : void 0;
13352
+ if (since === null || until === null) {
13353
+ return /* @__PURE__ */ new Set();
13354
+ }
13355
+ return new Set(database.getChunkIdsByBlameDate(since, until));
13356
+ }
13357
+ intersectChunkIdSets(first, second) {
13358
+ if (first === null) return second;
13359
+ if (second === null) return first;
13360
+ const [smaller, larger] = first.size <= second.size ? [first, second] : [second, first];
13361
+ return new Set(Array.from(smaller).filter((chunkId) => larger.has(chunkId)));
13362
+ }
12995
13363
  buildCandidateSnapshot(candidate) {
12996
13364
  return {
12997
13365
  id: candidate.id,
@@ -13006,13 +13374,16 @@ var Indexer = class _Indexer {
13006
13374
  buildCandidateSnapshotList(candidates) {
13007
13375
  return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
13008
13376
  }
13009
- searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
13010
- return this.searchCandidatesWithBranchPrefilter(
13011
- initialLimit,
13012
- store.count(),
13377
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch, temporalChunkIds) {
13378
+ const availableCount = temporalChunkIds?.size ?? store.count();
13379
+ if (availableCount === 0) return [];
13380
+ const allowedIds = temporalChunkIds === null ? void 0 : Array.from(temporalChunkIds);
13381
+ return this.searchCandidatesWithAllowedIds(
13382
+ Math.min(initialLimit, availableCount),
13383
+ availableCount,
13013
13384
  branchChunkIds,
13014
13385
  shouldPrefilterByBranch,
13015
- (requestedLimit) => store.search(embedding, requestedLimit),
13386
+ (requestedLimit) => store.search(embedding, requestedLimit, allowedIds),
13016
13387
  (candidate) => candidate.id
13017
13388
  );
13018
13389
  }
@@ -13037,7 +13408,9 @@ var Indexer = class _Indexer {
13037
13408
  const rerankTopN = this.config.search.rerankTopN;
13038
13409
  const filterByBranch = options?.filterByBranch ?? true;
13039
13410
  const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
13411
+ const prioritizeSourcePaths = sourceIntent || options?.prioritizeSourcePaths === true;
13040
13412
  const identifierHints = extractIdentifierHints(query);
13413
+ const candidateLimit = maxResults * (prioritizeSourcePaths ? 12 : 4);
13041
13414
  this.logger.search("debug", "Starting search", {
13042
13415
  query,
13043
13416
  maxResults,
@@ -13068,25 +13441,28 @@ var Indexer = class _Indexer {
13068
13441
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
13069
13442
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
13070
13443
  }
13444
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
13071
13445
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
13072
13446
  const prefilterMs = performance2.now() - prefilterStartTime;
13073
13447
  const vectorStartTime = performance2.now();
13074
13448
  const semanticCandidates = embedding ? this.searchSemanticCandidates(
13075
13449
  store,
13076
13450
  embedding,
13077
- maxResults * 4,
13451
+ candidateLimit,
13078
13452
  branchChunkIds,
13079
- shouldPrefilterByBranch
13453
+ shouldPrefilterByBranch,
13454
+ temporalChunkIds
13080
13455
  ) : [];
13081
13456
  const vectorMs = performance2.now() - vectorStartTime;
13082
13457
  const keywordStartTime = performance2.now();
13083
13458
  const keywordCandidates = await this.keywordSearch(
13084
13459
  query,
13085
- maxResults * 4,
13460
+ candidateLimit,
13086
13461
  store,
13087
13462
  invertedIndex,
13088
13463
  branchChunkIds,
13089
- shouldPrefilterByBranch
13464
+ shouldPrefilterByBranch,
13465
+ temporalChunkIds
13090
13466
  );
13091
13467
  const keywordMs = performance2.now() - keywordStartTime;
13092
13468
  const scopedSemanticCandidates = semanticCandidates.filter(
@@ -13108,7 +13484,7 @@ var Indexer = class _Indexer {
13108
13484
  rerankTopN,
13109
13485
  limit: maxResults,
13110
13486
  hybridWeight: rankingHybridWeight,
13111
- prioritizeSourcePaths: sourceIntent
13487
+ prioritizeSourcePaths
13112
13488
  });
13113
13489
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
13114
13490
  definitionIntent: options?.definitionIntent === true,
@@ -13144,10 +13520,11 @@ var Indexer = class _Indexer {
13144
13520
  branchSymbolIds,
13145
13521
  maxResults,
13146
13522
  union,
13147
- sourceIntent
13523
+ sourceIntent,
13524
+ options?.definitionIntent === true && ((options.directory?.trim().length ?? 0) > 0 || (options.fileType?.trim().length ?? 0) > 0)
13148
13525
  );
13149
13526
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
13150
- const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13527
+ const primaryLane = options?.definitionIntent === true ? mergeTieredResults(symbolLane, prePrimaryLane, maxResults * 4) : mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13151
13528
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
13152
13529
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
13153
13530
  const baseFiltered = tiered.filter(
@@ -13242,14 +13619,18 @@ var Indexer = class _Indexer {
13242
13619
  })
13243
13620
  );
13244
13621
  }
13245
- async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
13622
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false, temporalChunkIds = null) {
13246
13623
  const normalizedLimit = Math.max(0, Math.floor(limit));
13247
13624
  if (normalizedLimit === 0) return [];
13248
- const scoreEntries = this.searchCandidatesWithBranchPrefilter(
13625
+ const allowedChunkIds = this.intersectChunkIdSets(
13626
+ shouldPrefilterByBranch ? branchChunkIds : null,
13627
+ temporalChunkIds
13628
+ );
13629
+ const scoreEntries = this.searchCandidatesWithAllowedIds(
13249
13630
  normalizedLimit,
13250
13631
  invertedIndex.getDocumentCount(),
13251
- branchChunkIds,
13252
- shouldPrefilterByBranch,
13632
+ allowedChunkIds,
13633
+ allowedChunkIds !== null,
13253
13634
  (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
13254
13635
  ([chunkId]) => chunkId
13255
13636
  );
@@ -13334,7 +13715,17 @@ var Indexer = class _Indexer {
13334
13715
  );
13335
13716
  const currentFileHashes = /* @__PURE__ */ new Map();
13336
13717
  for (const file of files) {
13337
- currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
13718
+ let hash;
13719
+ try {
13720
+ hash = hashFile(file.path);
13721
+ } catch (error) {
13722
+ this.logger.warn("Skipped unreadable file during freshness check", {
13723
+ path: file.path,
13724
+ error: getErrorMessage4(error)
13725
+ });
13726
+ return { readable: false, current: false, reason: "unreadable" };
13727
+ }
13728
+ currentFileHashes.set(this.toStoredFilePath(file.path), hash);
13338
13729
  }
13339
13730
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
13340
13731
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -13360,69 +13751,87 @@ var Indexer = class _Indexer {
13360
13751
  async forceIndex(onProgress) {
13361
13752
  return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
13362
13753
  await this.ensureInitializedUnlocked(recoveredOwners);
13363
- await this.clearIndexUnlocked();
13754
+ const recovery = this.beginClearRecoveryState();
13755
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13756
+ this.finishClearRecoveryState();
13364
13757
  return this.indexUnlocked(onProgress, [], true);
13365
13758
  });
13366
13759
  }
13367
13760
  async clearIndex() {
13368
13761
  await this.withIndexMutationLease("clear", async (recoveredOwners) => {
13369
13762
  await this.ensureInitializedUnlocked(recoveredOwners);
13370
- await this.clearIndexUnlocked();
13763
+ const recovery = this.beginClearRecoveryState();
13764
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13371
13765
  });
13372
13766
  }
13373
- async clearIndexUnlocked() {
13767
+ clearGlobalIndexDataUnlocked(projectRoot = this.projectRoot) {
13374
13768
  const { store, invertedIndex, database } = this.requireLoadedIndexState();
13375
- if (this.config.scope === "global") {
13376
- store.load();
13377
- invertedIndex.load();
13378
- this.loadFileHashCache();
13379
- const roots = this.getScopedRoots();
13380
- const compatibility = this.checkCompatibility();
13381
- const allMetadata = store.getAllMetadata();
13382
- const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13383
- if (!compatibility.compatible && hasForeignData) {
13384
- if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
13385
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13386
- this.clearScopedFileHashCache(roots);
13387
- this.clearScopedFailedBatches(roots);
13388
- database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
13389
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13769
+ const clearedBranchKeys = database.getAllBranches();
13770
+ store.clear();
13771
+ store.save();
13772
+ invertedIndex.clear();
13773
+ this.saveInvertedIndex(invertedIndex);
13774
+ this.fileHashCache.clear();
13775
+ this.saveFileHashCache();
13776
+ database.clearAllIndexedData();
13777
+ this.deleteBranchCommitMetadata(database, clearedBranchKeys);
13778
+ this.clearFailedBatchState();
13779
+ database.deleteMetadata("index.version");
13780
+ database.deleteMetadata("index.pathStorageVersion");
13781
+ database.deleteMetadata("index.embeddingProvider");
13782
+ database.deleteMetadata("index.embeddingModel");
13783
+ database.deleteMetadata("index.embeddingDimensions");
13784
+ database.deleteMetadata("index.embeddingStrategyVersion");
13785
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13786
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13787
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash));
13788
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey(projectIdentityHash));
13789
+ database.deleteMetadata("index.createdAt");
13790
+ database.deleteMetadata("index.updatedAt");
13791
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13792
+ }
13793
+ clearGlobalIndexUnlocked(projectRoot = this.projectRoot, roots = this.getScopedRoots(), recoveryDecision) {
13794
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13795
+ store.load();
13796
+ invertedIndex.load();
13797
+ this.loadFileHashCache();
13798
+ const compatibility = this.checkCompatibility();
13799
+ const compatibilityDecision = recoveryDecision ?? (compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible");
13800
+ const allMetadata = store.getAllMetadata();
13801
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData(projectRoot, roots) || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13802
+ if (compatibilityDecision !== "compatible" && hasForeignData) {
13803
+ if (compatibilityDecision === "embedding-strategy-mismatch") {
13804
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13805
+ this.clearScopedFileHashCache(roots);
13806
+ this.clearScopedFailedBatches(roots);
13807
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13808
+ database.setMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash), "true");
13809
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13810
+ database.deleteMetadata(this.getProjectMigrationFinalizedMetadataKey(projectIdentityHash));
13811
+ if (projectRoot === this.projectRoot) {
13390
13812
  this.indexCompatibility = { compatible: true };
13391
- return;
13392
13813
  }
13393
- throw new Error(
13394
- `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.`
13395
- );
13396
- }
13397
- if (!hasForeignData) {
13398
- const clearedBranchKeys2 = database.getAllBranches();
13399
- store.clear();
13400
- store.save();
13401
- invertedIndex.clear();
13402
- this.saveInvertedIndex(invertedIndex);
13403
- this.fileHashCache.clear();
13404
- this.saveFileHashCache();
13405
- database.clearAllIndexedData();
13406
- this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
13407
- this.clearFailedBatchState();
13408
- database.deleteMetadata("index.version");
13409
- database.deleteMetadata("index.pathStorageVersion");
13410
- database.deleteMetadata("index.embeddingProvider");
13411
- database.deleteMetadata("index.embeddingModel");
13412
- database.deleteMetadata("index.embeddingDimensions");
13413
- database.deleteMetadata("index.embeddingStrategyVersion");
13414
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13415
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13416
- database.deleteMetadata(this.getLegacyMigrationMetadataKey());
13417
- database.deleteMetadata("index.createdAt");
13418
- database.deleteMetadata("index.updatedAt");
13419
- this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13420
13814
  return;
13421
13815
  }
13422
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13423
- this.clearScopedFileHashCache(roots);
13424
- this.clearScopedFailedBatches(roots);
13816
+ throw new Error(
13817
+ `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.`
13818
+ );
13819
+ }
13820
+ if (!hasForeignData) {
13821
+ this.clearGlobalIndexDataUnlocked(projectRoot);
13822
+ return;
13823
+ }
13824
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13825
+ this.clearScopedFileHashCache(roots);
13826
+ this.clearScopedFailedBatches(roots);
13827
+ if (projectRoot === this.projectRoot) {
13425
13828
  this.indexCompatibility = compatibility;
13829
+ }
13830
+ }
13831
+ async clearIndexUnlocked(recoveryDecision) {
13832
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13833
+ if (this.config.scope === "global") {
13834
+ this.clearGlobalIndexUnlocked(this.projectRoot, this.getScopedRoots(), recoveryDecision);
13426
13835
  return;
13427
13836
  }
13428
13837
  if (!this.isProjectOwnedIndexPath()) {
@@ -13588,6 +13997,7 @@ var Indexer = class _Indexer {
13588
13997
  )) {
13589
13998
  const chunks = retryBatch.map(({ chunk }) => chunk);
13590
13999
  const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
14000
+ this.restoreMissingChunkRows(database, chunks);
13591
14001
  const batchResult = await this.processPendingChunkBatch(chunks, {
13592
14002
  store,
13593
14003
  provider,
@@ -13602,6 +14012,7 @@ var Indexer = class _Indexer {
13602
14012
  forceReembed: false,
13603
14013
  reuseCachedEmbeddings: false,
13604
14014
  incrementRepeatedFailures: false,
14015
+ forceSingleItemBatches: true,
13605
14016
  onSucceeded: (succeededChunks) => {
13606
14017
  database.addChunksToBranchBatch(
13607
14018
  this.getBranchCatalogKey(),
@@ -13623,9 +14034,12 @@ var Indexer = class _Indexer {
13623
14034
  this.saveInvertedIndex(invertedIndex);
13624
14035
  }
13625
14036
  if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
13626
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13627
- this.saveIndexMetadata(configuredProviderInfo);
13628
- this.indexCompatibility = { compatible: true };
14037
+ const migrationFinalized = database.getMetadata(this.getProjectMigrationFinalizedMetadataKey()) === "true";
14038
+ if (migrationFinalized) {
14039
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
14040
+ this.saveIndexMetadata(configuredProviderInfo);
14041
+ this.indexCompatibility = { compatible: true };
14042
+ }
13629
14043
  }
13630
14044
  return { succeeded, failed, remaining };
13631
14045
  }
@@ -13647,7 +14061,8 @@ var Indexer = class _Indexer {
13647
14061
  latestById.set(chunkId, {
13648
14062
  attemptCount: batch.attemptCount,
13649
14063
  error: batch.error,
13650
- lastAttempt: batch.lastAttempt
14064
+ lastAttempt: batch.lastAttempt,
14065
+ chunks: [rawChunk]
13651
14066
  });
13652
14067
  }
13653
14068
  }
@@ -13714,6 +14129,7 @@ var Indexer = class _Indexer {
13714
14129
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
13715
14130
  );
13716
14131
  }
14132
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
13717
14133
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
13718
14134
  const prefilterMs = performance2.now() - prefilterStartTime;
13719
14135
  const vectorStartTime = performance2.now();
@@ -13722,7 +14138,8 @@ var Indexer = class _Indexer {
13722
14138
  embedding,
13723
14139
  limit * 2,
13724
14140
  branchChunkIds,
13725
- shouldPrefilterByBranch
14141
+ shouldPrefilterByBranch,
14142
+ temporalChunkIds
13726
14143
  );
13727
14144
  const vectorMs = performance2.now() - vectorStartTime;
13728
14145
  if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
@@ -13840,9 +14257,9 @@ var Indexer = class _Indexer {
13840
14257
  this.requireReadableComponents(readIssues, "database");
13841
14258
  let shortest = [];
13842
14259
  for (const branchKey of this.getBranchCatalogKeys()) {
13843
- const path28 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
13844
- if (path28.length > 0 && (shortest.length === 0 || path28.length < shortest.length)) {
13845
- shortest = path28;
14260
+ const path30 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
14261
+ if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14262
+ shortest = path30;
13846
14263
  }
13847
14264
  }
13848
14265
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -13890,13 +14307,13 @@ var Indexer = class _Indexer {
13890
14307
  }
13891
14308
  }
13892
14309
  if (!found) continue;
13893
- const path28 = [];
14310
+ const path30 = [];
13894
14311
  let currentSymbolId = toSymbolId;
13895
14312
  while (true) {
13896
14313
  const symbol = symbolsById.get(currentSymbolId);
13897
14314
  if (!symbol) break;
13898
14315
  const parent = parentBySymbolId.get(currentSymbolId);
13899
- path28.push({
14316
+ path30.push({
13900
14317
  symbolId: symbol.id,
13901
14318
  symbolName: symbol.name,
13902
14319
  filePath: symbol.filePath,
@@ -13906,9 +14323,9 @@ var Indexer = class _Indexer {
13906
14323
  if (!parent) break;
13907
14324
  currentSymbolId = parent.parentId;
13908
14325
  }
13909
- path28.reverse();
13910
- if (path28.length > 0 && (shortest.length === 0 || path28.length < shortest.length)) {
13911
- shortest = path28;
14326
+ path30.reverse();
14327
+ if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14328
+ shortest = path30;
13912
14329
  }
13913
14330
  }
13914
14331
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14244,7 +14661,6 @@ var Indexer = class _Indexer {
14244
14661
  this.store = null;
14245
14662
  this.invertedIndex = null;
14246
14663
  this.provider = null;
14247
- this.reranker = null;
14248
14664
  this.configuredProviderInfo = null;
14249
14665
  this.indexCompatibility = null;
14250
14666
  this.initializationMode = "none";
@@ -14472,9 +14888,11 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14472
14888
  contextLines: options.contextLines,
14473
14889
  metadataOnly: options.metadataOnly,
14474
14890
  definitionIntent: options.definitionIntent,
14891
+ prioritizeSourcePaths: options.prioritizeSourcePaths,
14475
14892
  blameAuthor: options.blameAuthor,
14476
14893
  blameSha: options.blameSha,
14477
14894
  blameSince: options.blameSince,
14895
+ blameUntil: options.blameUntil,
14478
14896
  trace: options.trace
14479
14897
  });
14480
14898
  }
@@ -14520,7 +14938,9 @@ async function findSimilarCode(projectRoot, host, code, options = {}) {
14520
14938
  fileType: options.fileType,
14521
14939
  directory: options.directory,
14522
14940
  chunkType: options.chunkType,
14523
- excludeFile: options.excludeFile
14941
+ excludeFile: options.excludeFile,
14942
+ blameSince: options.blameSince,
14943
+ blameUntil: options.blameUntil
14524
14944
  });
14525
14945
  }
14526
14946
  async function implementationLookup(projectRoot, host, query, options = {}) {
@@ -14569,12 +14989,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14569
14989
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14570
14990
  return { from: fromResolution, to: toResolution, path: [] };
14571
14991
  }
14572
- const path28 = await indexer.findCallPathBySymbolIds(
14992
+ const path30 = await indexer.findCallPathBySymbolIds(
14573
14993
  fromResolution.symbolId,
14574
14994
  toResolution.symbolId,
14575
14995
  maxDepth
14576
14996
  );
14577
- return { from: fromResolution, to: toResolution, path: path28 };
14997
+ return { from: fromResolution, to: toResolution, path: path30 };
14578
14998
  }
14579
14999
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
14580
15000
  const root = getProjectRoot(projectRoot, host);
@@ -14800,8 +15220,8 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
14800
15220
  }
14801
15221
  }
14802
15222
  try {
14803
- const stat4 = statSync5(normalizedPath2);
14804
- if (!stat4.isDirectory()) {
15223
+ const stat5 = statSync5(normalizedPath2);
15224
+ if (!stat5.isDirectory()) {
14805
15225
  return `Error: Path is not a directory: ${normalizedPath2}`;
14806
15226
  }
14807
15227
  } catch (error) {
@@ -14849,8 +15269,8 @@ function listKnowledgeBases(projectRoot, host) {
14849
15269
  `;
14850
15270
  if (exists) {
14851
15271
  try {
14852
- const stat4 = statSync5(resolvedPath);
14853
- result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
15272
+ const stat5 = statSync5(resolvedPath);
15273
+ result += ` Type: ${stat5.isDirectory() ? "Directory" : "File"}
14854
15274
  `;
14855
15275
  } catch {
14856
15276
  }
@@ -14891,7 +15311,7 @@ Run /index to rebuild the index without the removed knowledge base.`;
14891
15311
  }
14892
15312
 
14893
15313
  // src/watcher/file-watcher.ts
14894
- import { existsSync as existsSync13 } from "fs";
15314
+ import { existsSync as existsSync13, statSync as statSync6 } from "fs";
14895
15315
 
14896
15316
  // node_modules/chokidar/index.js
14897
15317
  import { EventEmitter as EventEmitter2 } from "events";
@@ -14983,7 +15403,7 @@ var ReaddirpStream = class extends Readable {
14983
15403
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
14984
15404
  const statMethod = opts.lstat ? lstat : stat;
14985
15405
  if (wantBigintFsStats) {
14986
- this._stat = (path28) => statMethod(path28, { bigint: true });
15406
+ this._stat = (path30) => statMethod(path30, { bigint: true });
14987
15407
  } else {
14988
15408
  this._stat = statMethod;
14989
15409
  }
@@ -15008,8 +15428,8 @@ var ReaddirpStream = class extends Readable {
15008
15428
  const par = this.parent;
15009
15429
  const fil = par && par.files;
15010
15430
  if (fil && fil.length > 0) {
15011
- const { path: path28, depth } = par;
15012
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path28));
15431
+ const { path: path30, depth } = par;
15432
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path30));
15013
15433
  const awaited = await Promise.all(slice);
15014
15434
  for (const entry of awaited) {
15015
15435
  if (!entry)
@@ -15049,20 +15469,20 @@ var ReaddirpStream = class extends Readable {
15049
15469
  this.reading = false;
15050
15470
  }
15051
15471
  }
15052
- async _exploreDir(path28, depth) {
15472
+ async _exploreDir(path30, depth) {
15053
15473
  let files;
15054
15474
  try {
15055
- files = await readdir(path28, this._rdOptions);
15475
+ files = await readdir(path30, this._rdOptions);
15056
15476
  } catch (error) {
15057
15477
  this._onError(error);
15058
15478
  }
15059
- return { files, depth, path: path28 };
15479
+ return { files, depth, path: path30 };
15060
15480
  }
15061
- async _formatEntry(dirent, path28) {
15481
+ async _formatEntry(dirent, path30) {
15062
15482
  let entry;
15063
15483
  const basename9 = this._isDirent ? dirent.name : dirent;
15064
15484
  try {
15065
- const fullPath = presolve(pjoin(path28, basename9));
15485
+ const fullPath = presolve(pjoin(path30, basename9));
15066
15486
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
15067
15487
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
15068
15488
  } catch (err) {
@@ -15462,16 +15882,16 @@ var delFromSet = (main, prop, item) => {
15462
15882
  };
15463
15883
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
15464
15884
  var FsWatchInstances = /* @__PURE__ */ new Map();
15465
- function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
15885
+ function createFsWatchInstance(path30, options, listener, errHandler, emitRaw) {
15466
15886
  const handleEvent = (rawEvent, evPath) => {
15467
- listener(path28);
15468
- emitRaw(rawEvent, evPath, { watchedPath: path28 });
15469
- if (evPath && path28 !== evPath) {
15470
- fsWatchBroadcast(sp.resolve(path28, evPath), KEY_LISTENERS, sp.join(path28, evPath));
15887
+ listener(path30);
15888
+ emitRaw(rawEvent, evPath, { watchedPath: path30 });
15889
+ if (evPath && path30 !== evPath) {
15890
+ fsWatchBroadcast(sp.resolve(path30, evPath), KEY_LISTENERS, sp.join(path30, evPath));
15471
15891
  }
15472
15892
  };
15473
15893
  try {
15474
- return fs_watch(path28, {
15894
+ return fs_watch(path30, {
15475
15895
  persistent: options.persistent
15476
15896
  }, handleEvent);
15477
15897
  } catch (error) {
@@ -15487,12 +15907,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
15487
15907
  listener(val1, val2, val3);
15488
15908
  });
15489
15909
  };
15490
- var setFsWatchListener = (path28, fullPath, options, handlers) => {
15910
+ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15491
15911
  const { listener, errHandler, rawEmitter } = handlers;
15492
15912
  let cont = FsWatchInstances.get(fullPath);
15493
15913
  let watcher;
15494
15914
  if (!options.persistent) {
15495
- watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
15915
+ watcher = createFsWatchInstance(path30, options, listener, errHandler, rawEmitter);
15496
15916
  if (!watcher)
15497
15917
  return;
15498
15918
  return watcher.close.bind(watcher);
@@ -15503,7 +15923,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15503
15923
  addAndConvert(cont, KEY_RAW, rawEmitter);
15504
15924
  } else {
15505
15925
  watcher = createFsWatchInstance(
15506
- path28,
15926
+ path30,
15507
15927
  options,
15508
15928
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
15509
15929
  errHandler,
@@ -15518,7 +15938,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15518
15938
  cont.watcherUnusable = true;
15519
15939
  if (isWindows && error.code === "EPERM") {
15520
15940
  try {
15521
- const fd = await open(path28, "r");
15941
+ const fd = await open(path30, "r");
15522
15942
  await fd.close();
15523
15943
  broadcastErr(error);
15524
15944
  } catch (err) {
@@ -15549,7 +15969,7 @@ var setFsWatchListener = (path28, fullPath, options, handlers) => {
15549
15969
  };
15550
15970
  };
15551
15971
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
15552
- var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
15972
+ var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
15553
15973
  const { listener, rawEmitter } = handlers;
15554
15974
  let cont = FsWatchFileInstances.get(fullPath);
15555
15975
  const copts = cont && cont.options;
@@ -15571,7 +15991,7 @@ var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
15571
15991
  });
15572
15992
  const currmtime = curr.mtimeMs;
15573
15993
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
15574
- foreach(cont.listeners, (listener2) => listener2(path28, curr));
15994
+ foreach(cont.listeners, (listener2) => listener2(path30, curr));
15575
15995
  }
15576
15996
  })
15577
15997
  };
@@ -15601,13 +16021,13 @@ var NodeFsHandler = class {
15601
16021
  * @param listener on fs change
15602
16022
  * @returns closer for the watcher instance
15603
16023
  */
15604
- _watchWithNodeFs(path28, listener) {
16024
+ _watchWithNodeFs(path30, listener) {
15605
16025
  const opts = this.fsw.options;
15606
- const directory = sp.dirname(path28);
15607
- const basename9 = sp.basename(path28);
16026
+ const directory = sp.dirname(path30);
16027
+ const basename9 = sp.basename(path30);
15608
16028
  const parent = this.fsw._getWatchedDir(directory);
15609
16029
  parent.add(basename9);
15610
- const absolutePath = sp.resolve(path28);
16030
+ const absolutePath = sp.resolve(path30);
15611
16031
  const options = {
15612
16032
  persistent: opts.persistent
15613
16033
  };
@@ -15617,12 +16037,12 @@ var NodeFsHandler = class {
15617
16037
  if (opts.usePolling) {
15618
16038
  const enableBin = opts.interval !== opts.binaryInterval;
15619
16039
  options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
15620
- closer = setFsWatchFileListener(path28, absolutePath, options, {
16040
+ closer = setFsWatchFileListener(path30, absolutePath, options, {
15621
16041
  listener,
15622
16042
  rawEmitter: this.fsw._emitRaw
15623
16043
  });
15624
16044
  } else {
15625
- closer = setFsWatchListener(path28, absolutePath, options, {
16045
+ closer = setFsWatchListener(path30, absolutePath, options, {
15626
16046
  listener,
15627
16047
  errHandler: this._boundHandleError,
15628
16048
  rawEmitter: this.fsw._emitRaw
@@ -15644,7 +16064,7 @@ var NodeFsHandler = class {
15644
16064
  let prevStats = stats;
15645
16065
  if (parent.has(basename9))
15646
16066
  return;
15647
- const listener = async (path28, newStats) => {
16067
+ const listener = async (path30, newStats) => {
15648
16068
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
15649
16069
  return;
15650
16070
  if (!newStats || newStats.mtimeMs === 0) {
@@ -15658,11 +16078,11 @@ var NodeFsHandler = class {
15658
16078
  this.fsw._emit(EV.CHANGE, file, newStats2);
15659
16079
  }
15660
16080
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
15661
- this.fsw._closeFile(path28);
16081
+ this.fsw._closeFile(path30);
15662
16082
  prevStats = newStats2;
15663
16083
  const closer2 = this._watchWithNodeFs(file, listener);
15664
16084
  if (closer2)
15665
- this.fsw._addPathCloser(path28, closer2);
16085
+ this.fsw._addPathCloser(path30, closer2);
15666
16086
  } else {
15667
16087
  prevStats = newStats2;
15668
16088
  }
@@ -15694,7 +16114,7 @@ var NodeFsHandler = class {
15694
16114
  * @param item basename of this item
15695
16115
  * @returns true if no more processing is needed for this entry.
15696
16116
  */
15697
- async _handleSymlink(entry, directory, path28, item) {
16117
+ async _handleSymlink(entry, directory, path30, item) {
15698
16118
  if (this.fsw.closed) {
15699
16119
  return;
15700
16120
  }
@@ -15704,7 +16124,7 @@ var NodeFsHandler = class {
15704
16124
  this.fsw._incrReadyCount();
15705
16125
  let linkPath;
15706
16126
  try {
15707
- linkPath = await fsrealpath(path28);
16127
+ linkPath = await fsrealpath(path30);
15708
16128
  } catch (e) {
15709
16129
  this.fsw._emitReady();
15710
16130
  return true;
@@ -15714,12 +16134,12 @@ var NodeFsHandler = class {
15714
16134
  if (dir.has(item)) {
15715
16135
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
15716
16136
  this.fsw._symlinkPaths.set(full, linkPath);
15717
- this.fsw._emit(EV.CHANGE, path28, entry.stats);
16137
+ this.fsw._emit(EV.CHANGE, path30, entry.stats);
15718
16138
  }
15719
16139
  } else {
15720
16140
  dir.add(item);
15721
16141
  this.fsw._symlinkPaths.set(full, linkPath);
15722
- this.fsw._emit(EV.ADD, path28, entry.stats);
16142
+ this.fsw._emit(EV.ADD, path30, entry.stats);
15723
16143
  }
15724
16144
  this.fsw._emitReady();
15725
16145
  return true;
@@ -15749,9 +16169,9 @@ var NodeFsHandler = class {
15749
16169
  return;
15750
16170
  }
15751
16171
  const item = entry.path;
15752
- let path28 = sp.join(directory, item);
16172
+ let path30 = sp.join(directory, item);
15753
16173
  current.add(item);
15754
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
16174
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path30, item)) {
15755
16175
  return;
15756
16176
  }
15757
16177
  if (this.fsw.closed) {
@@ -15760,11 +16180,11 @@ var NodeFsHandler = class {
15760
16180
  }
15761
16181
  if (item === target || !target && !previous.has(item)) {
15762
16182
  this.fsw._incrReadyCount();
15763
- path28 = sp.join(dir, sp.relative(dir, path28));
15764
- this._addToNodeFs(path28, initialAdd, wh, depth + 1);
16183
+ path30 = sp.join(dir, sp.relative(dir, path30));
16184
+ this._addToNodeFs(path30, initialAdd, wh, depth + 1);
15765
16185
  }
15766
16186
  }).on(EV.ERROR, this._boundHandleError);
15767
- return new Promise((resolve15, reject) => {
16187
+ return new Promise((resolve17, reject) => {
15768
16188
  if (!stream)
15769
16189
  return reject();
15770
16190
  stream.once(STR_END, () => {
@@ -15773,7 +16193,7 @@ var NodeFsHandler = class {
15773
16193
  return;
15774
16194
  }
15775
16195
  const wasThrottled = throttler ? throttler.clear() : false;
15776
- resolve15(void 0);
16196
+ resolve17(void 0);
15777
16197
  previous.getChildren().filter((item) => {
15778
16198
  return item !== directory && !current.has(item);
15779
16199
  }).forEach((item) => {
@@ -15830,13 +16250,13 @@ var NodeFsHandler = class {
15830
16250
  * @param depth Child path actually targeted for watch
15831
16251
  * @param target Child path actually targeted for watch
15832
16252
  */
15833
- async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
16253
+ async _addToNodeFs(path30, initialAdd, priorWh, depth, target) {
15834
16254
  const ready = this.fsw._emitReady;
15835
- if (this.fsw._isIgnored(path28) || this.fsw.closed) {
16255
+ if (this.fsw._isIgnored(path30) || this.fsw.closed) {
15836
16256
  ready();
15837
16257
  return false;
15838
16258
  }
15839
- const wh = this.fsw._getWatchHelpers(path28);
16259
+ const wh = this.fsw._getWatchHelpers(path30);
15840
16260
  if (priorWh) {
15841
16261
  wh.filterPath = (entry) => priorWh.filterPath(entry);
15842
16262
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -15852,8 +16272,8 @@ var NodeFsHandler = class {
15852
16272
  const follow = this.fsw.options.followSymlinks;
15853
16273
  let closer;
15854
16274
  if (stats.isDirectory()) {
15855
- const absPath = sp.resolve(path28);
15856
- const targetPath = follow ? await fsrealpath(path28) : path28;
16275
+ const absPath = sp.resolve(path30);
16276
+ const targetPath = follow ? await fsrealpath(path30) : path30;
15857
16277
  if (this.fsw.closed)
15858
16278
  return;
15859
16279
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -15863,29 +16283,29 @@ var NodeFsHandler = class {
15863
16283
  this.fsw._symlinkPaths.set(absPath, targetPath);
15864
16284
  }
15865
16285
  } else if (stats.isSymbolicLink()) {
15866
- const targetPath = follow ? await fsrealpath(path28) : path28;
16286
+ const targetPath = follow ? await fsrealpath(path30) : path30;
15867
16287
  if (this.fsw.closed)
15868
16288
  return;
15869
16289
  const parent = sp.dirname(wh.watchPath);
15870
16290
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
15871
16291
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
15872
- closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
16292
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path30, wh, targetPath);
15873
16293
  if (this.fsw.closed)
15874
16294
  return;
15875
16295
  if (targetPath !== void 0) {
15876
- this.fsw._symlinkPaths.set(sp.resolve(path28), targetPath);
16296
+ this.fsw._symlinkPaths.set(sp.resolve(path30), targetPath);
15877
16297
  }
15878
16298
  } else {
15879
16299
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
15880
16300
  }
15881
16301
  ready();
15882
16302
  if (closer)
15883
- this.fsw._addPathCloser(path28, closer);
16303
+ this.fsw._addPathCloser(path30, closer);
15884
16304
  return false;
15885
16305
  } catch (error) {
15886
16306
  if (this.fsw._handleError(error)) {
15887
16307
  ready();
15888
- return path28;
16308
+ return path30;
15889
16309
  }
15890
16310
  }
15891
16311
  }
@@ -15917,35 +16337,35 @@ function createPattern(matcher) {
15917
16337
  if (matcher.path === string)
15918
16338
  return true;
15919
16339
  if (matcher.recursive) {
15920
- const relative12 = sp2.relative(matcher.path, string);
15921
- if (!relative12) {
16340
+ const relative14 = sp2.relative(matcher.path, string);
16341
+ if (!relative14) {
15922
16342
  return false;
15923
16343
  }
15924
- return !relative12.startsWith("..") && !sp2.isAbsolute(relative12);
16344
+ return !relative14.startsWith("..") && !sp2.isAbsolute(relative14);
15925
16345
  }
15926
16346
  return false;
15927
16347
  };
15928
16348
  }
15929
16349
  return () => false;
15930
16350
  }
15931
- function normalizePath2(path28) {
15932
- if (typeof path28 !== "string")
16351
+ function normalizePath2(path30) {
16352
+ if (typeof path30 !== "string")
15933
16353
  throw new Error("string expected");
15934
- path28 = sp2.normalize(path28);
15935
- path28 = path28.replace(/\\/g, "/");
16354
+ path30 = sp2.normalize(path30);
16355
+ path30 = path30.replace(/\\/g, "/");
15936
16356
  let prepend = false;
15937
- if (path28.startsWith("//"))
16357
+ if (path30.startsWith("//"))
15938
16358
  prepend = true;
15939
- path28 = path28.replace(DOUBLE_SLASH_RE, "/");
16359
+ path30 = path30.replace(DOUBLE_SLASH_RE, "/");
15940
16360
  if (prepend)
15941
- path28 = "/" + path28;
15942
- return path28;
16361
+ path30 = "/" + path30;
16362
+ return path30;
15943
16363
  }
15944
16364
  function matchPatterns(patterns, testString, stats) {
15945
- const path28 = normalizePath2(testString);
16365
+ const path30 = normalizePath2(testString);
15946
16366
  for (let index = 0; index < patterns.length; index++) {
15947
16367
  const pattern = patterns[index];
15948
- if (pattern(path28, stats)) {
16368
+ if (pattern(path30, stats)) {
15949
16369
  return true;
15950
16370
  }
15951
16371
  }
@@ -15983,19 +16403,19 @@ var toUnix = (string) => {
15983
16403
  }
15984
16404
  return str;
15985
16405
  };
15986
- var normalizePathToUnix = (path28) => toUnix(sp2.normalize(toUnix(path28)));
15987
- var normalizeIgnored = (cwd = "") => (path28) => {
15988
- if (typeof path28 === "string") {
15989
- return normalizePathToUnix(sp2.isAbsolute(path28) ? path28 : sp2.join(cwd, path28));
16406
+ var normalizePathToUnix = (path30) => toUnix(sp2.normalize(toUnix(path30)));
16407
+ var normalizeIgnored = (cwd = "") => (path30) => {
16408
+ if (typeof path30 === "string") {
16409
+ return normalizePathToUnix(sp2.isAbsolute(path30) ? path30 : sp2.join(cwd, path30));
15990
16410
  } else {
15991
- return path28;
16411
+ return path30;
15992
16412
  }
15993
16413
  };
15994
- var getAbsolutePath = (path28, cwd) => {
15995
- if (sp2.isAbsolute(path28)) {
15996
- return path28;
16414
+ var getAbsolutePath = (path30, cwd) => {
16415
+ if (sp2.isAbsolute(path30)) {
16416
+ return path30;
15997
16417
  }
15998
- return sp2.join(cwd, path28);
16418
+ return sp2.join(cwd, path30);
15999
16419
  };
16000
16420
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
16001
16421
  var DirEntry = class {
@@ -16060,10 +16480,10 @@ var WatchHelper = class {
16060
16480
  dirParts;
16061
16481
  followSymlinks;
16062
16482
  statMethod;
16063
- constructor(path28, follow, fsw) {
16483
+ constructor(path30, follow, fsw) {
16064
16484
  this.fsw = fsw;
16065
- const watchPath = path28;
16066
- this.path = path28 = path28.replace(REPLACER_RE, "");
16485
+ const watchPath = path30;
16486
+ this.path = path30 = path30.replace(REPLACER_RE, "");
16067
16487
  this.watchPath = watchPath;
16068
16488
  this.fullWatchPath = sp2.resolve(watchPath);
16069
16489
  this.dirParts = [];
@@ -16203,20 +16623,20 @@ var FSWatcher = class extends EventEmitter2 {
16203
16623
  this._closePromise = void 0;
16204
16624
  let paths = unifyPaths(paths_);
16205
16625
  if (cwd) {
16206
- paths = paths.map((path28) => {
16207
- const absPath = getAbsolutePath(path28, cwd);
16626
+ paths = paths.map((path30) => {
16627
+ const absPath = getAbsolutePath(path30, cwd);
16208
16628
  return absPath;
16209
16629
  });
16210
16630
  }
16211
- paths.forEach((path28) => {
16212
- this._removeIgnoredPath(path28);
16631
+ paths.forEach((path30) => {
16632
+ this._removeIgnoredPath(path30);
16213
16633
  });
16214
16634
  this._userIgnored = void 0;
16215
16635
  if (!this._readyCount)
16216
16636
  this._readyCount = 0;
16217
16637
  this._readyCount += paths.length;
16218
- Promise.all(paths.map(async (path28) => {
16219
- const res = await this._nodeFsHandler._addToNodeFs(path28, !_internal, void 0, 0, _origAdd);
16638
+ Promise.all(paths.map(async (path30) => {
16639
+ const res = await this._nodeFsHandler._addToNodeFs(path30, !_internal, void 0, 0, _origAdd);
16220
16640
  if (res)
16221
16641
  this._emitReady();
16222
16642
  return res;
@@ -16238,17 +16658,17 @@ var FSWatcher = class extends EventEmitter2 {
16238
16658
  return this;
16239
16659
  const paths = unifyPaths(paths_);
16240
16660
  const { cwd } = this.options;
16241
- paths.forEach((path28) => {
16242
- if (!sp2.isAbsolute(path28) && !this._closers.has(path28)) {
16661
+ paths.forEach((path30) => {
16662
+ if (!sp2.isAbsolute(path30) && !this._closers.has(path30)) {
16243
16663
  if (cwd)
16244
- path28 = sp2.join(cwd, path28);
16245
- path28 = sp2.resolve(path28);
16664
+ path30 = sp2.join(cwd, path30);
16665
+ path30 = sp2.resolve(path30);
16246
16666
  }
16247
- this._closePath(path28);
16248
- this._addIgnoredPath(path28);
16249
- if (this._watched.has(path28)) {
16667
+ this._closePath(path30);
16668
+ this._addIgnoredPath(path30);
16669
+ if (this._watched.has(path30)) {
16250
16670
  this._addIgnoredPath({
16251
- path: path28,
16671
+ path: path30,
16252
16672
  recursive: true
16253
16673
  });
16254
16674
  }
@@ -16312,38 +16732,38 @@ var FSWatcher = class extends EventEmitter2 {
16312
16732
  * @param stats arguments to be passed with event
16313
16733
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
16314
16734
  */
16315
- async _emit(event, path28, stats) {
16735
+ async _emit(event, path30, stats) {
16316
16736
  if (this.closed)
16317
16737
  return;
16318
16738
  const opts = this.options;
16319
16739
  if (isWindows)
16320
- path28 = sp2.normalize(path28);
16740
+ path30 = sp2.normalize(path30);
16321
16741
  if (opts.cwd)
16322
- path28 = sp2.relative(opts.cwd, path28);
16323
- const args = [path28];
16742
+ path30 = sp2.relative(opts.cwd, path30);
16743
+ const args = [path30];
16324
16744
  if (stats != null)
16325
16745
  args.push(stats);
16326
16746
  const awf = opts.awaitWriteFinish;
16327
16747
  let pw;
16328
- if (awf && (pw = this._pendingWrites.get(path28))) {
16748
+ if (awf && (pw = this._pendingWrites.get(path30))) {
16329
16749
  pw.lastChange = /* @__PURE__ */ new Date();
16330
16750
  return this;
16331
16751
  }
16332
16752
  if (opts.atomic) {
16333
16753
  if (event === EVENTS.UNLINK) {
16334
- this._pendingUnlinks.set(path28, [event, ...args]);
16754
+ this._pendingUnlinks.set(path30, [event, ...args]);
16335
16755
  setTimeout(() => {
16336
- this._pendingUnlinks.forEach((entry, path29) => {
16756
+ this._pendingUnlinks.forEach((entry, path31) => {
16337
16757
  this.emit(...entry);
16338
16758
  this.emit(EVENTS.ALL, ...entry);
16339
- this._pendingUnlinks.delete(path29);
16759
+ this._pendingUnlinks.delete(path31);
16340
16760
  });
16341
16761
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
16342
16762
  return this;
16343
16763
  }
16344
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
16764
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path30)) {
16345
16765
  event = EVENTS.CHANGE;
16346
- this._pendingUnlinks.delete(path28);
16766
+ this._pendingUnlinks.delete(path30);
16347
16767
  }
16348
16768
  }
16349
16769
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -16361,16 +16781,16 @@ var FSWatcher = class extends EventEmitter2 {
16361
16781
  this.emitWithAll(event, args);
16362
16782
  }
16363
16783
  };
16364
- this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
16784
+ this._awaitWriteFinish(path30, awf.stabilityThreshold, event, awfEmit);
16365
16785
  return this;
16366
16786
  }
16367
16787
  if (event === EVENTS.CHANGE) {
16368
- const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
16788
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path30, 50);
16369
16789
  if (isThrottled)
16370
16790
  return this;
16371
16791
  }
16372
16792
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
16373
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path28) : path28;
16793
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path30) : path30;
16374
16794
  let stats2;
16375
16795
  try {
16376
16796
  stats2 = await stat3(fullPath);
@@ -16401,23 +16821,23 @@ var FSWatcher = class extends EventEmitter2 {
16401
16821
  * @param timeout duration of time to suppress duplicate actions
16402
16822
  * @returns tracking object or false if action should be suppressed
16403
16823
  */
16404
- _throttle(actionType, path28, timeout) {
16824
+ _throttle(actionType, path30, timeout) {
16405
16825
  if (!this._throttled.has(actionType)) {
16406
16826
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
16407
16827
  }
16408
16828
  const action = this._throttled.get(actionType);
16409
16829
  if (!action)
16410
16830
  throw new Error("invalid throttle");
16411
- const actionPath = action.get(path28);
16831
+ const actionPath = action.get(path30);
16412
16832
  if (actionPath) {
16413
16833
  actionPath.count++;
16414
16834
  return false;
16415
16835
  }
16416
16836
  let timeoutObject;
16417
16837
  const clear = () => {
16418
- const item = action.get(path28);
16838
+ const item = action.get(path30);
16419
16839
  const count = item ? item.count : 0;
16420
- action.delete(path28);
16840
+ action.delete(path30);
16421
16841
  clearTimeout(timeoutObject);
16422
16842
  if (item)
16423
16843
  clearTimeout(item.timeoutObject);
@@ -16425,7 +16845,7 @@ var FSWatcher = class extends EventEmitter2 {
16425
16845
  };
16426
16846
  timeoutObject = setTimeout(clear, timeout);
16427
16847
  const thr = { timeoutObject, clear, count: 0 };
16428
- action.set(path28, thr);
16848
+ action.set(path30, thr);
16429
16849
  return thr;
16430
16850
  }
16431
16851
  _incrReadyCount() {
@@ -16439,44 +16859,44 @@ var FSWatcher = class extends EventEmitter2 {
16439
16859
  * @param event
16440
16860
  * @param awfEmit Callback to be called when ready for event to be emitted.
16441
16861
  */
16442
- _awaitWriteFinish(path28, threshold, event, awfEmit) {
16862
+ _awaitWriteFinish(path30, threshold, event, awfEmit) {
16443
16863
  const awf = this.options.awaitWriteFinish;
16444
16864
  if (typeof awf !== "object")
16445
16865
  return;
16446
16866
  const pollInterval = awf.pollInterval;
16447
16867
  let timeoutHandler;
16448
- let fullPath = path28;
16449
- if (this.options.cwd && !sp2.isAbsolute(path28)) {
16450
- fullPath = sp2.join(this.options.cwd, path28);
16868
+ let fullPath = path30;
16869
+ if (this.options.cwd && !sp2.isAbsolute(path30)) {
16870
+ fullPath = sp2.join(this.options.cwd, path30);
16451
16871
  }
16452
16872
  const now2 = /* @__PURE__ */ new Date();
16453
16873
  const writes = this._pendingWrites;
16454
16874
  function awaitWriteFinishFn(prevStat) {
16455
16875
  statcb(fullPath, (err, curStat) => {
16456
- if (err || !writes.has(path28)) {
16876
+ if (err || !writes.has(path30)) {
16457
16877
  if (err && err.code !== "ENOENT")
16458
16878
  awfEmit(err);
16459
16879
  return;
16460
16880
  }
16461
16881
  const now3 = Number(/* @__PURE__ */ new Date());
16462
16882
  if (prevStat && curStat.size !== prevStat.size) {
16463
- writes.get(path28).lastChange = now3;
16883
+ writes.get(path30).lastChange = now3;
16464
16884
  }
16465
- const pw = writes.get(path28);
16885
+ const pw = writes.get(path30);
16466
16886
  const df = now3 - pw.lastChange;
16467
16887
  if (df >= threshold) {
16468
- writes.delete(path28);
16888
+ writes.delete(path30);
16469
16889
  awfEmit(void 0, curStat);
16470
16890
  } else {
16471
16891
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
16472
16892
  }
16473
16893
  });
16474
16894
  }
16475
- if (!writes.has(path28)) {
16476
- writes.set(path28, {
16895
+ if (!writes.has(path30)) {
16896
+ writes.set(path30, {
16477
16897
  lastChange: now2,
16478
16898
  cancelWait: () => {
16479
- writes.delete(path28);
16899
+ writes.delete(path30);
16480
16900
  clearTimeout(timeoutHandler);
16481
16901
  return event;
16482
16902
  }
@@ -16487,8 +16907,8 @@ var FSWatcher = class extends EventEmitter2 {
16487
16907
  /**
16488
16908
  * Determines whether user has asked to ignore this path.
16489
16909
  */
16490
- _isIgnored(path28, stats) {
16491
- if (this.options.atomic && DOT_RE.test(path28))
16910
+ _isIgnored(path30, stats) {
16911
+ if (this.options.atomic && DOT_RE.test(path30))
16492
16912
  return true;
16493
16913
  if (!this._userIgnored) {
16494
16914
  const { cwd } = this.options;
@@ -16498,17 +16918,17 @@ var FSWatcher = class extends EventEmitter2 {
16498
16918
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
16499
16919
  this._userIgnored = anymatch(list, void 0);
16500
16920
  }
16501
- return this._userIgnored(path28, stats);
16921
+ return this._userIgnored(path30, stats);
16502
16922
  }
16503
- _isntIgnored(path28, stat4) {
16504
- return !this._isIgnored(path28, stat4);
16923
+ _isntIgnored(path30, stat5) {
16924
+ return !this._isIgnored(path30, stat5);
16505
16925
  }
16506
16926
  /**
16507
16927
  * Provides a set of common helpers and properties relating to symlink handling.
16508
16928
  * @param path file or directory pattern being watched
16509
16929
  */
16510
- _getWatchHelpers(path28) {
16511
- return new WatchHelper(path28, this.options.followSymlinks, this);
16930
+ _getWatchHelpers(path30) {
16931
+ return new WatchHelper(path30, this.options.followSymlinks, this);
16512
16932
  }
16513
16933
  // Directory helpers
16514
16934
  // -----------------
@@ -16540,63 +16960,63 @@ var FSWatcher = class extends EventEmitter2 {
16540
16960
  * @param item base path of item/directory
16541
16961
  */
16542
16962
  _remove(directory, item, isDirectory) {
16543
- const path28 = sp2.join(directory, item);
16544
- const fullPath = sp2.resolve(path28);
16545
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path28) || this._watched.has(fullPath);
16546
- if (!this._throttle("remove", path28, 100))
16963
+ const path30 = sp2.join(directory, item);
16964
+ const fullPath = sp2.resolve(path30);
16965
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path30) || this._watched.has(fullPath);
16966
+ if (!this._throttle("remove", path30, 100))
16547
16967
  return;
16548
16968
  if (!isDirectory && this._watched.size === 1) {
16549
16969
  this.add(directory, item, true);
16550
16970
  }
16551
- const wp = this._getWatchedDir(path28);
16971
+ const wp = this._getWatchedDir(path30);
16552
16972
  const nestedDirectoryChildren = wp.getChildren();
16553
- nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
16973
+ nestedDirectoryChildren.forEach((nested) => this._remove(path30, nested));
16554
16974
  const parent = this._getWatchedDir(directory);
16555
16975
  const wasTracked = parent.has(item);
16556
16976
  parent.remove(item);
16557
16977
  if (this._symlinkPaths.has(fullPath)) {
16558
16978
  this._symlinkPaths.delete(fullPath);
16559
16979
  }
16560
- let relPath = path28;
16980
+ let relPath = path30;
16561
16981
  if (this.options.cwd)
16562
- relPath = sp2.relative(this.options.cwd, path28);
16982
+ relPath = sp2.relative(this.options.cwd, path30);
16563
16983
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
16564
16984
  const event = this._pendingWrites.get(relPath).cancelWait();
16565
16985
  if (event === EVENTS.ADD)
16566
16986
  return;
16567
16987
  }
16568
- this._watched.delete(path28);
16988
+ this._watched.delete(path30);
16569
16989
  this._watched.delete(fullPath);
16570
16990
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
16571
- if (wasTracked && !this._isIgnored(path28))
16572
- this._emit(eventName, path28);
16573
- this._closePath(path28);
16991
+ if (wasTracked && !this._isIgnored(path30))
16992
+ this._emit(eventName, path30);
16993
+ this._closePath(path30);
16574
16994
  }
16575
16995
  /**
16576
16996
  * Closes all watchers for a path
16577
16997
  */
16578
- _closePath(path28) {
16579
- this._closeFile(path28);
16580
- const dir = sp2.dirname(path28);
16581
- this._getWatchedDir(dir).remove(sp2.basename(path28));
16998
+ _closePath(path30) {
16999
+ this._closeFile(path30);
17000
+ const dir = sp2.dirname(path30);
17001
+ this._getWatchedDir(dir).remove(sp2.basename(path30));
16582
17002
  }
16583
17003
  /**
16584
17004
  * Closes only file-specific watchers
16585
17005
  */
16586
- _closeFile(path28) {
16587
- const closers = this._closers.get(path28);
17006
+ _closeFile(path30) {
17007
+ const closers = this._closers.get(path30);
16588
17008
  if (!closers)
16589
17009
  return;
16590
17010
  closers.forEach((closer) => closer());
16591
- this._closers.delete(path28);
17011
+ this._closers.delete(path30);
16592
17012
  }
16593
- _addPathCloser(path28, closer) {
17013
+ _addPathCloser(path30, closer) {
16594
17014
  if (!closer)
16595
17015
  return;
16596
- let list = this._closers.get(path28);
17016
+ let list = this._closers.get(path30);
16597
17017
  if (!list) {
16598
17018
  list = [];
16599
- this._closers.set(path28, list);
17019
+ this._closers.set(path30, list);
16600
17020
  }
16601
17021
  list.push(closer);
16602
17022
  }
@@ -16626,12 +17046,291 @@ function watch(paths, options = {}) {
16626
17046
  var chokidar_default = { watch, FSWatcher };
16627
17047
 
16628
17048
  // src/watcher/file-watcher.ts
17049
+ import * as path23 from "path";
17050
+
17051
+ // src/watcher/native-recursive-watcher.ts
17052
+ import { watch as watch2 } from "fs";
16629
17053
  import * as path21 from "path";
17054
+ var NativeRecursiveWatcher = class {
17055
+ constructor(root, onChange, options = {}) {
17056
+ this.root = root;
17057
+ this.onChange = onChange;
17058
+ this.watchFactory = options.watchFactory ?? this.defaultWatchFactory;
17059
+ this.onError = options.onError;
17060
+ }
17061
+ root;
17062
+ onChange;
17063
+ watcher = null;
17064
+ listenerToken = 0;
17065
+ watchFactory;
17066
+ onError;
17067
+ start() {
17068
+ if (this.watcher) return;
17069
+ const token = ++this.listenerToken;
17070
+ const listener = (_eventType, filename) => {
17071
+ if (this.watcher === null || this.listenerToken !== token) return;
17072
+ const absolutePath = this.toAbsolutePath(filename);
17073
+ const nextResult = this.onChange(absolutePath);
17074
+ if (nextResult instanceof Promise) {
17075
+ void nextResult.catch((error) => {
17076
+ console.error("[codebase-index] Error handling native watcher event:", error);
17077
+ });
17078
+ }
17079
+ };
17080
+ const watcher = this.watchFactory(this.root, listener, {
17081
+ persistent: true,
17082
+ recursive: true
17083
+ });
17084
+ watcher.on?.("error", (error) => {
17085
+ if (this.watcher === watcher && this.listenerToken === token) {
17086
+ this.onError?.(error);
17087
+ }
17088
+ });
17089
+ this.watcher = watcher;
17090
+ }
17091
+ async stop() {
17092
+ const watcher = this.watcher;
17093
+ this.watcher = null;
17094
+ this.listenerToken += 1;
17095
+ if (!watcher) return;
17096
+ await watcher.close();
17097
+ }
17098
+ toAbsolutePath(filename) {
17099
+ if (filename == null) return null;
17100
+ const normalizedFilename = typeof filename === "string" ? filename : filename.toString();
17101
+ const absolutePath = path21.resolve(this.root, normalizedFilename);
17102
+ const relativePath = path21.relative(this.root, absolutePath);
17103
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path21.sep}`) || path21.isAbsolute(relativePath);
17104
+ return outsideRoot ? null : absolutePath;
17105
+ }
17106
+ defaultWatchFactory = (root, listener, options) => watch2(root, options, listener);
17107
+ };
17108
+
17109
+ // src/watcher/snapshot.ts
17110
+ import * as fsPromises4 from "fs/promises";
17111
+ import * as path22 from "path";
17112
+ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17113
+ const normalizedProjectRoot = path22.resolve(projectRoot);
17114
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17115
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17116
+ const maxDepth = config.indexing?.maxDepth ?? -1;
17117
+ const snapshot = /* @__PURE__ */ new Map();
17118
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17119
+ const includeFile = async (filePath) => {
17120
+ const normalizedPath2 = path22.resolve(filePath);
17121
+ if (!shouldIncludeFile(normalizedPath2, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
17122
+ const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17123
+ if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17124
+ };
17125
+ const walk = async (directoryPath, depth) => {
17126
+ let entries;
17127
+ try {
17128
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
17129
+ } catch (error) {
17130
+ if (isMissingFsError(error)) return;
17131
+ if (isPermissionFsError(error)) {
17132
+ unreadablePrefixes.add(path22.resolve(directoryPath));
17133
+ return;
17134
+ }
17135
+ throw error;
17136
+ }
17137
+ for (const entry of entries) {
17138
+ const fullPath = path22.join(directoryPath, entry.name);
17139
+ const relativePath = path22.relative(normalizedProjectRoot, fullPath);
17140
+ if (entry.isDirectory()) {
17141
+ if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
17142
+ if (ignoreFilter.ignores(relativePath)) continue;
17143
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17144
+ } else if (entry.isFile()) {
17145
+ await includeFile(fullPath);
17146
+ }
17147
+ }
17148
+ };
17149
+ await walk(normalizedProjectRoot, 0);
17150
+ await includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths);
17151
+ return { entries: snapshot, unreadablePrefixes };
17152
+ }
17153
+ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
17154
+ const normalizedProjectRoot = path22.resolve(projectRoot);
17155
+ const normalizedTargetPath = path22.resolve(targetPath);
17156
+ if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
17157
+ return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
17158
+ }
17159
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17160
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17161
+ const maxDepth = config.indexing?.maxDepth ?? -1;
17162
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path22.resolve(configPath)));
17163
+ const snapshot = /* @__PURE__ */ new Map();
17164
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17165
+ const includeFile = async (filePath) => {
17166
+ const normalizedPath2 = path22.resolve(filePath);
17167
+ if (!explicitConfigPaths.has(normalizedPath2) && !shouldIncludeFile(
17168
+ normalizedPath2,
17169
+ normalizedProjectRoot,
17170
+ includePatterns,
17171
+ config.exclude,
17172
+ ignoreFilter
17173
+ )) return;
17174
+ const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17175
+ if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17176
+ };
17177
+ const walk = async (directoryPath, depth) => {
17178
+ let entries;
17179
+ try {
17180
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
17181
+ } catch (error) {
17182
+ if (isMissingFsError(error)) return;
17183
+ if (isPermissionFsError(error)) {
17184
+ unreadablePrefixes.add(path22.resolve(directoryPath));
17185
+ return;
17186
+ }
17187
+ throw error;
17188
+ }
17189
+ for (const entry of entries) {
17190
+ const fullPath = path22.join(directoryPath, entry.name);
17191
+ const relativePath = path22.relative(normalizedProjectRoot, fullPath);
17192
+ if (entry.isDirectory()) {
17193
+ if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
17194
+ if (ignoreFilter.ignores(relativePath)) continue;
17195
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17196
+ } else if (entry.isFile()) {
17197
+ await includeFile(fullPath);
17198
+ }
17199
+ }
17200
+ };
17201
+ const targetStat = await readStatIfFile(normalizedTargetPath, unreadablePrefixes);
17202
+ if (targetStat) await includeFile(normalizedTargetPath);
17203
+ else await walk(normalizedTargetPath, 0);
17204
+ await includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, normalizedTargetPath);
17205
+ return { entries: snapshot, unreadablePrefixes };
17206
+ }
17207
+ function completeFileSnapshot(previous, scan) {
17208
+ const completed = new Map(scan.entries);
17209
+ for (const unreadablePrefix of scan.unreadablePrefixes) {
17210
+ for (const [entryPath, entry] of previous) {
17211
+ if (isWithinPath(unreadablePrefix, entryPath) && !completed.has(entryPath)) completed.set(entryPath, entry);
17212
+ }
17213
+ }
17214
+ return completed;
17215
+ }
17216
+ async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
17217
+ for (const configPath of [...new Set(configPaths.map((value) => path22.resolve(value)))]) {
17218
+ if (snapshot.has(configPath)) continue;
17219
+ const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
17220
+ if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
17221
+ }
17222
+ }
17223
+ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, targetPath) {
17224
+ await includeExplicitConfigPaths(
17225
+ snapshot,
17226
+ unreadablePrefixes,
17227
+ configPaths.filter((configPath) => isWithinPath(targetPath, path22.resolve(configPath)))
17228
+ );
17229
+ }
17230
+ function isWithinPath(parentPath, childPath) {
17231
+ const relativePath = path22.relative(parentPath, childPath);
17232
+ return relativePath === "" || !relativePath.startsWith(`..${path22.sep}`) && relativePath !== ".." && !path22.isAbsolute(relativePath);
17233
+ }
17234
+ async function readStatIfFile(filePath, unreadablePrefixes) {
17235
+ try {
17236
+ const stat5 = await fsPromises4.stat(filePath);
17237
+ return stat5.isFile() ? stat5 : null;
17238
+ } catch (error) {
17239
+ if (isMissingFsError(error)) return null;
17240
+ if (isPermissionFsError(error)) {
17241
+ unreadablePrefixes.add(path22.resolve(filePath));
17242
+ return null;
17243
+ }
17244
+ throw error;
17245
+ }
17246
+ }
17247
+ function isMissingFsError(error) {
17248
+ return error instanceof Error && ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
17249
+ }
17250
+ function isPermissionFsError(error) {
17251
+ return error instanceof Error && ["EACCES", "EPERM"].includes(error.code ?? "");
17252
+ }
17253
+ var diffTypeOrder = { add: 0, change: 1, unlink: 2 };
17254
+ function diffFileSnapshots(previous, current, forcedChanges = /* @__PURE__ */ new Set()) {
17255
+ const changes = [];
17256
+ for (const [filePath, previousEntry] of previous) {
17257
+ const currentEntry = current.get(filePath);
17258
+ if (!currentEntry) changes.push({ type: "unlink", path: filePath });
17259
+ else if (forcedChanges.has(filePath) || currentEntry.size !== previousEntry.size || currentEntry.mtimeMs !== previousEntry.mtimeMs) {
17260
+ changes.push({ type: "change", path: filePath });
17261
+ }
17262
+ }
17263
+ for (const [filePath] of current) {
17264
+ if (!previous.has(filePath)) changes.push({ type: "add", path: filePath });
17265
+ }
17266
+ return changes.sort((left, right) => left.path.localeCompare(right.path) || diffTypeOrder[left.type] - diffTypeOrder[right.type]);
17267
+ }
17268
+
17269
+ // src/watcher/snapshot-reconciler.ts
17270
+ var FileSnapshotReconciler = class {
17271
+ constructor(projectRoot, config, configPaths) {
17272
+ this.projectRoot = projectRoot;
17273
+ this.config = config;
17274
+ this.configPaths = configPaths;
17275
+ }
17276
+ projectRoot;
17277
+ config;
17278
+ configPaths;
17279
+ snapshot = null;
17280
+ reconciliationTail = Promise.resolve();
17281
+ async initialize() {
17282
+ this.snapshot = (await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths)).entries;
17283
+ }
17284
+ async reconcile(invalidations = []) {
17285
+ if (this.snapshot === null) {
17286
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
17287
+ }
17288
+ const reconciliation = this.reconciliationTail.then(async () => {
17289
+ const previousSnapshot = this.snapshot;
17290
+ if (previousSnapshot === null) {
17291
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
17292
+ }
17293
+ const normalizedInvalidations = invalidations.map((invalidation) => typeof invalidation === "string" || invalidation === null ? { path: invalidation, forceChange: false } : { path: invalidation.path, forceChange: invalidation.forceChange === true });
17294
+ const scopedPaths = normalizedInvalidations.map((invalidation) => invalidation.path).filter((filePath) => filePath !== null);
17295
+ const scan = scopedPaths.length === 0 || scopedPaths.length !== normalizedInvalidations.length ? await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths) : await this.reconcilePaths(previousSnapshot, scopedPaths);
17296
+ const nextSnapshot = completeFileSnapshot(previousSnapshot, scan);
17297
+ const forcedChanges = new Set(normalizedInvalidations.filter((invalidation) => invalidation.path !== null && invalidation.forceChange).map((invalidation) => invalidation.path));
17298
+ const changes = diffFileSnapshots(previousSnapshot, nextSnapshot, forcedChanges);
17299
+ this.snapshot = nextSnapshot;
17300
+ return changes;
17301
+ });
17302
+ this.reconciliationTail = reconciliation.then(() => void 0, () => void 0);
17303
+ return reconciliation;
17304
+ }
17305
+ async reconcilePaths(previousSnapshot, invalidatedPaths) {
17306
+ const scopes = this.getScopes(invalidatedPaths);
17307
+ const entries = new Map(previousSnapshot);
17308
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
17309
+ for (const scope of scopes) {
17310
+ for (const previousPath of entries.keys()) {
17311
+ if (isWithinPath(scope, previousPath)) entries.delete(previousPath);
17312
+ }
17313
+ const scopedScan = await buildFileSnapshotForPathScan(this.projectRoot, this.config, this.configPaths, scope);
17314
+ for (const [filePath, entry] of scopedScan.entries) entries.set(filePath, entry);
17315
+ for (const unreadablePrefix of scopedScan.unreadablePrefixes) unreadablePrefixes.add(unreadablePrefix);
17316
+ }
17317
+ return { entries, unreadablePrefixes };
17318
+ }
17319
+ getScopes(invalidatedPaths) {
17320
+ const uniquePaths = [...new Set(invalidatedPaths)].sort((left, right) => left.length - right.length);
17321
+ return uniquePaths.filter((candidate, index) => !uniquePaths.slice(0, index).some(
17322
+ (ancestor) => isWithinPath(ancestor, candidate)
17323
+ ));
17324
+ }
17325
+ };
17326
+
17327
+ // src/watcher/file-watcher.ts
16630
17328
  var FileWatcher = class {
16631
17329
  watcher = null;
16632
17330
  projectRoot;
16633
17331
  config;
16634
17332
  configPath;
17333
+ backend;
16635
17334
  projectConfigPaths;
16636
17335
  pendingChanges = /* @__PURE__ */ new Map();
16637
17336
  debounceTimer = null;
@@ -16641,44 +17340,74 @@ var FileWatcher = class {
16641
17340
  resolveReady = null;
16642
17341
  pollingFallbackAttempted = false;
16643
17342
  pendingClose = null;
17343
+ startupReadySignals = 1;
17344
+ nativeWatcher = null;
17345
+ nativeReconciler = null;
17346
+ nativeSetupGeneration = 0;
17347
+ nativeStarting = false;
17348
+ nativeInitializing = false;
17349
+ nativeReconcileTimer = null;
17350
+ nativeInvalidatedPaths = /* @__PURE__ */ new Map();
17351
+ configPathStates = /* @__PURE__ */ new Map();
16644
17352
  constructor(projectRoot, config, host, options = {}) {
16645
17353
  this.projectRoot = projectRoot;
16646
17354
  this.config = config;
17355
+ this.backend = options.backend ?? "auto";
16647
17356
  this.configPath = options.configPath;
16648
17357
  this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
16649
17358
  }
16650
17359
  start(handler) {
16651
- if (this.watcher) {
17360
+ if (this.watcher || this.nativeWatcher || this.nativeStarting) {
16652
17361
  return;
16653
17362
  }
16654
17363
  this.onChanges = handler;
16655
17364
  this.pollingFallbackAttempted = false;
16656
17365
  this.resetReady();
17366
+ if (this.shouldUseNativeWatcher()) {
17367
+ if (this.hasExternalConfigWatchTarget()) {
17368
+ this.setStartupReadySignals(2);
17369
+ this.startExternalConfigWatcher();
17370
+ }
17371
+ this.nativeStarting = true;
17372
+ void this.createNativeWatcher();
17373
+ return;
17374
+ }
16657
17375
  this.createWatcher();
16658
17376
  }
16659
17377
  resetReady() {
16660
- this.readyPromise = new Promise((resolve15) => {
16661
- this.resolveReady = resolve15;
17378
+ this.readyPromise = new Promise((resolve17) => {
17379
+ this.resolveReady = resolve17;
16662
17380
  });
17381
+ this.startupReadySignals = 1;
16663
17382
  }
16664
- createWatcher(usePolling = false) {
16665
- const ignoreFilter = createIgnoreFilter(this.projectRoot);
16666
- let watchTargets = this.projectRoot;
16667
- if (this.configPath) {
16668
- watchTargets = [this.projectRoot, this.configPath];
16669
- } else {
16670
- const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
16671
- const relativeConfigPath = path21.relative(this.projectRoot, projectConfigPath);
16672
- return this.isOutsideProjectPath(relativeConfigPath);
16673
- }).map((projectConfigPath) => existsSync13(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path21.dirname(projectConfigPath)));
16674
- const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
16675
- if (uniqueExternalConfigTargets.length > 0) {
16676
- watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
16677
- }
17383
+ setStartupReadySignals(expectedSignals) {
17384
+ if (!this.readyPromise) {
17385
+ return;
17386
+ }
17387
+ this.startupReadySignals = Math.max(0, expectedSignals);
17388
+ }
17389
+ reportStartupReadySignal() {
17390
+ if (!this.readyPromise || !this.resolveReady) {
17391
+ return;
17392
+ }
17393
+ if (this.startupReadySignals <= 0) {
17394
+ return;
16678
17395
  }
17396
+ this.startupReadySignals -= 1;
17397
+ if (this.startupReadySignals !== 0) {
17398
+ return;
17399
+ }
17400
+ this.resolveReady();
17401
+ this.resolveReady = null;
17402
+ }
17403
+ createWatcher(watchTargets, usePolling = false, reportsStartupReady = true) {
17404
+ let reportedStartupReady = false;
17405
+ this.configPathStates = this.getConfigPathStates();
17406
+ const ignoreFilter = createIgnoreFilter(this.projectRoot);
17407
+ const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
16679
17408
  const watcherOptions = {
16680
17409
  ignored: (filePath) => {
16681
- const relativePath = path21.relative(this.projectRoot, filePath);
17410
+ const relativePath = path23.relative(this.projectRoot, filePath);
16682
17411
  if (!relativePath) return false;
16683
17412
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
16684
17413
  return false;
@@ -16686,10 +17415,10 @@ var FileWatcher = class {
16686
17415
  if (this.isOutsideProjectPath(relativePath)) {
16687
17416
  return true;
16688
17417
  }
16689
- if (hasFilteredPathSegment(relativePath, path21.sep)) {
17418
+ if (hasFilteredPathSegment(relativePath, path23.sep)) {
16690
17419
  return true;
16691
17420
  }
16692
- if (isRestrictedDirectory(relativePath, path21.sep)) {
17421
+ if (isRestrictedDirectory(relativePath, path23.sep)) {
16693
17422
  return true;
16694
17423
  }
16695
17424
  if (ignoreFilter.ignores(relativePath)) {
@@ -16722,10 +17451,13 @@ var FileWatcher = class {
16722
17451
  watcher = new FSWatcher(watcherOptions);
16723
17452
  }
16724
17453
  this.watcher = watcher;
16725
- watcher.once("ready", () => {
17454
+ watcher.on("ready", () => {
16726
17455
  if (this.watcher !== watcher) return;
16727
- this.resolveReady?.();
16728
- this.resolveReady = null;
17456
+ this.reconcileConfigPathStates();
17457
+ if (reportsStartupReady) {
17458
+ this.reportStartupReadySignal();
17459
+ reportedStartupReady = true;
17460
+ }
16729
17461
  });
16730
17462
  watcher.on("error", (error) => {
16731
17463
  const err = error instanceof Error ? error : null;
@@ -16739,10 +17471,13 @@ var FileWatcher = class {
16739
17471
  console.error("[codebase-index] Failed to close exhausted file watcher:", closeError);
16740
17472
  });
16741
17473
  if (this.onChanges) {
17474
+ const replacementReportsStartupReady = reportsStartupReady || reportedStartupReady;
16742
17475
  if (!this.resolveReady) {
16743
17476
  this.resetReady();
17477
+ } else if (reportedStartupReady) {
17478
+ this.startupReadySignals += 1;
16744
17479
  }
16745
- this.createWatcher(true);
17480
+ this.createWatcher(resolvedWatchTargets, true, replacementReportsStartupReady);
16746
17481
  } else {
16747
17482
  this.watcher = null;
16748
17483
  }
@@ -16753,13 +17488,166 @@ var FileWatcher = class {
16753
17488
  watcher.on("add", (filePath) => this.handleChange(watcher, "add", filePath));
16754
17489
  watcher.on("change", (filePath) => this.handleChange(watcher, "change", filePath));
16755
17490
  watcher.on("unlink", (filePath) => this.handleChange(watcher, "unlink", filePath));
16756
- watcher.add(watchTargets);
17491
+ watcher.add(resolvedWatchTargets);
17492
+ }
17493
+ shouldUseNativeWatcher() {
17494
+ if (this.backend === "chokidar") {
17495
+ return false;
17496
+ }
17497
+ return true;
17498
+ }
17499
+ getFullChokidarWatchTargets() {
17500
+ if (this.configPath) {
17501
+ return [this.projectRoot, this.configPath];
17502
+ }
17503
+ const externalConfigTargets = this.getExternalConfigWatchTargets();
17504
+ if (externalConfigTargets.length === 0) {
17505
+ return this.projectRoot;
17506
+ }
17507
+ return [this.projectRoot, ...externalConfigTargets];
17508
+ }
17509
+ getExternalConfigWatchTargets() {
17510
+ return [...new Set(
17511
+ this.projectConfigPaths.filter((projectConfigPath) => {
17512
+ const relativeConfigPath = path23.relative(this.projectRoot, projectConfigPath);
17513
+ return this.isOutsideProjectPath(relativeConfigPath);
17514
+ }).map((projectConfigPath) => {
17515
+ if (existsSync13(projectConfigPath)) {
17516
+ return projectConfigPath;
17517
+ }
17518
+ return this.getNearestExistingDirectory(path23.dirname(projectConfigPath));
17519
+ })
17520
+ )];
17521
+ }
17522
+ hasExternalConfigWatchTarget() {
17523
+ return this.getExternalConfigWatchTargets().length > 0;
17524
+ }
17525
+ startExternalConfigWatcher(usePolling = false) {
17526
+ const externalTargets = this.getExternalConfigWatchTargets();
17527
+ if (externalTargets.length === 0) {
17528
+ return;
17529
+ }
17530
+ this.createWatcher(externalTargets, usePolling);
17531
+ }
17532
+ async createNativeWatcher() {
17533
+ const generation = ++this.nativeSetupGeneration;
17534
+ const reconciler = new FileSnapshotReconciler(this.projectRoot, this.config, this.projectConfigPaths);
17535
+ const watcher = new NativeRecursiveWatcher(
17536
+ this.projectRoot,
17537
+ (filePath) => this.scheduleNativeReconciliation(generation, filePath),
17538
+ { onError: (error) => void this.fallbackFromNativeWatcher(generation, error) }
17539
+ );
17540
+ this.nativeReconciler = reconciler;
17541
+ this.nativeWatcher = watcher;
17542
+ this.nativeInitializing = true;
17543
+ try {
17544
+ watcher.start();
17545
+ if (!this.isCurrentNativeSetup(generation)) {
17546
+ await watcher.stop();
17547
+ return;
17548
+ }
17549
+ await reconciler.initialize();
17550
+ if (!this.isCurrentNativeSetup(generation) || this.nativeWatcher !== watcher) {
17551
+ await watcher.stop();
17552
+ return;
17553
+ }
17554
+ this.nativeStarting = false;
17555
+ this.nativeInitializing = false;
17556
+ await this.reconcileNativeWatcherWithPendingInvalidations(generation);
17557
+ this.reportStartupReadySignal();
17558
+ } catch (error) {
17559
+ if (!this.isCurrentNativeSetup(generation)) return;
17560
+ this.nativeInitializing = false;
17561
+ if (this.nativeWatcher) {
17562
+ await this.fallbackFromNativeWatcher(generation, error);
17563
+ return;
17564
+ }
17565
+ this.nativeStarting = false;
17566
+ const externalWatcher = this.watcher;
17567
+ this.watcher = null;
17568
+ this.nativeReconciler = null;
17569
+ await externalWatcher?.close();
17570
+ console.warn("[codebase-index] Native recursive watcher unavailable; using Chokidar fallback.", error);
17571
+ this.setStartupReadySignals(1);
17572
+ this.createWatcher();
17573
+ }
17574
+ }
17575
+ isCurrentNativeSetup(generation) {
17576
+ return this.nativeSetupGeneration === generation && this.onChanges !== null;
17577
+ }
17578
+ scheduleNativeReconciliation(generation, filePath) {
17579
+ if (!this.isCurrentNativeSetup(generation)) return;
17580
+ const requiresFullReconciliation = filePath === path23.join(this.projectRoot, ".gitignore");
17581
+ const invalidatedPath = requiresFullReconciliation ? null : filePath;
17582
+ this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
17583
+ if (this.nativeReconcileTimer) {
17584
+ clearTimeout(this.nativeReconcileTimer);
17585
+ }
17586
+ this.nativeReconcileTimer = setTimeout(() => {
17587
+ this.nativeReconcileTimer = null;
17588
+ void this.reconcileNativeWatcherFromQueue(generation);
17589
+ }, 100);
17590
+ }
17591
+ reconcileNativeWatcherFromQueue(generation) {
17592
+ if (!this.isCurrentNativeSetup(generation) || this.nativeInitializing) return;
17593
+ const invalidatedPaths = this.popNativeInvalidations();
17594
+ if (invalidatedPaths.length === 0) return;
17595
+ void this.reconcileNativeWatcher(generation, invalidatedPaths);
17596
+ }
17597
+ async reconcileNativeWatcher(generation, invalidatedPaths) {
17598
+ if (!this.isCurrentNativeSetup(generation) || !this.nativeReconciler) return;
17599
+ try {
17600
+ const reconciler = this.nativeReconciler;
17601
+ const changes = await reconciler.reconcile(invalidatedPaths);
17602
+ if (!this.isCurrentNativeSetup(generation) || this.nativeReconciler !== reconciler) return;
17603
+ this.recordChanges(changes);
17604
+ } catch (error) {
17605
+ await this.fallbackFromNativeWatcher(generation, error);
17606
+ }
17607
+ }
17608
+ async reconcileNativeWatcherWithPendingInvalidations(generation) {
17609
+ const invalidatedPaths = this.popNativeInvalidations();
17610
+ if (invalidatedPaths.length === 0) return;
17611
+ await this.reconcileNativeWatcher(generation, invalidatedPaths);
17612
+ }
17613
+ popNativeInvalidations() {
17614
+ if (this.nativeInvalidatedPaths.size === 0) return [];
17615
+ const invalidations = [...this.nativeInvalidatedPaths].map(([invalidatedPath, forceChange]) => ({
17616
+ path: invalidatedPath,
17617
+ forceChange
17618
+ }));
17619
+ this.nativeInvalidatedPaths.clear();
17620
+ return invalidations;
17621
+ }
17622
+ async fallbackFromNativeWatcher(generation, error) {
17623
+ if (!this.isCurrentNativeSetup(generation)) return;
17624
+ const watcher = this.nativeWatcher;
17625
+ const externalWatcher = this.watcher;
17626
+ this.nativeWatcher = null;
17627
+ this.watcher = null;
17628
+ this.nativeReconciler = null;
17629
+ this.nativeStarting = false;
17630
+ this.nativeInitializing = false;
17631
+ this.nativeSetupGeneration += 1;
17632
+ if (this.nativeReconcileTimer) {
17633
+ clearTimeout(this.nativeReconcileTimer);
17634
+ this.nativeReconcileTimer = null;
17635
+ }
17636
+ this.nativeInvalidatedPaths.clear();
17637
+ this.setStartupReadySignals(1);
17638
+ console.warn("[codebase-index] Native recursive watcher failed; using Chokidar fallback.", error);
17639
+ await watcher?.stop();
17640
+ await externalWatcher?.close();
17641
+ if (this.onChanges) {
17642
+ this.createWatcher();
17643
+ }
16757
17644
  }
16758
17645
  handleChange(watcher, type, filePath) {
16759
17646
  if (this.watcher !== watcher) {
16760
17647
  return;
16761
17648
  }
16762
17649
  if (this.isProjectConfigPath(filePath)) {
17650
+ this.updateConfigPathState(filePath);
16763
17651
  this.pendingChanges.set(filePath, type);
16764
17652
  this.scheduleFlush();
16765
17653
  return;
@@ -16774,27 +17662,33 @@ var FileWatcher = class {
16774
17662
  )) {
16775
17663
  return;
16776
17664
  }
16777
- this.pendingChanges.set(filePath, type);
17665
+ this.recordChanges([{ path: filePath, type }]);
17666
+ }
17667
+ recordChanges(changes) {
17668
+ if (changes.length === 0) return;
17669
+ for (const change of changes) {
17670
+ this.pendingChanges.set(change.path, change.type);
17671
+ }
16778
17672
  this.scheduleFlush();
16779
17673
  }
16780
17674
  isProjectConfigPath(filePath) {
16781
- const relativePath = path21.relative(this.projectRoot, filePath);
16782
- const normalizedRelativePath = path21.normalize(relativePath);
17675
+ const relativePath = path23.relative(this.projectRoot, filePath);
17676
+ const normalizedRelativePath = path23.normalize(relativePath);
16783
17677
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
16784
17678
  }
16785
17679
  isProjectConfigPathOrAncestor(relativePath) {
16786
- const normalizedRelativePath = path21.normalize(relativePath);
17680
+ const normalizedRelativePath = path23.normalize(relativePath);
16787
17681
  return this.getProjectConfigRelativePaths().some(
16788
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path21.sep}`)
17682
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path23.sep}`)
16789
17683
  );
16790
17684
  }
16791
17685
  isOutsideProjectPath(relativePath) {
16792
- return relativePath === ".." || relativePath.startsWith(`..${path21.sep}`) || path21.isAbsolute(relativePath);
17686
+ return relativePath === ".." || relativePath.startsWith(`..${path23.sep}`) || path23.isAbsolute(relativePath);
16793
17687
  }
16794
17688
  getNearestExistingDirectory(directoryPath) {
16795
17689
  let candidate = directoryPath;
16796
17690
  while (!existsSync13(candidate)) {
16797
- const parent = path21.dirname(candidate);
17691
+ const parent = path23.dirname(candidate);
16798
17692
  if (parent === candidate) break;
16799
17693
  candidate = parent;
16800
17694
  }
@@ -16802,9 +17696,51 @@ var FileWatcher = class {
16802
17696
  }
16803
17697
  getProjectConfigRelativePaths() {
16804
17698
  return this.projectConfigPaths.map(
16805
- (configPath) => path21.normalize(path21.relative(this.projectRoot, configPath))
17699
+ (configPath) => path23.normalize(path23.relative(this.projectRoot, configPath))
16806
17700
  );
16807
17701
  }
17702
+ getConfigPathStates() {
17703
+ const states = /* @__PURE__ */ new Map();
17704
+ for (const configPath of this.projectConfigPaths) {
17705
+ const state = this.getConfigPathState(configPath);
17706
+ if (state) states.set(configPath, state);
17707
+ }
17708
+ return states;
17709
+ }
17710
+ getConfigPathState(configPath) {
17711
+ try {
17712
+ const stats = statSync6(configPath);
17713
+ return stats.isFile() ? { mtimeMs: stats.mtimeMs, size: stats.size } : void 0;
17714
+ } catch (error) {
17715
+ void error;
17716
+ return void 0;
17717
+ }
17718
+ }
17719
+ updateConfigPathState(configPath) {
17720
+ const state = this.getConfigPathState(configPath);
17721
+ if (state) {
17722
+ this.configPathStates.set(configPath, state);
17723
+ } else {
17724
+ this.configPathStates.delete(configPath);
17725
+ }
17726
+ }
17727
+ reconcileConfigPathStates() {
17728
+ const nextStates = this.getConfigPathStates();
17729
+ const changes = [];
17730
+ for (const configPath of this.projectConfigPaths) {
17731
+ const previous = this.configPathStates.get(configPath);
17732
+ const next = nextStates.get(configPath);
17733
+ if (!previous && next) {
17734
+ changes.push({ path: configPath, type: "add" });
17735
+ } else if (previous && !next) {
17736
+ changes.push({ path: configPath, type: "unlink" });
17737
+ } else if (previous && next && (previous.size !== next.size || previous.mtimeMs !== next.mtimeMs)) {
17738
+ changes.push({ path: configPath, type: "change" });
17739
+ }
17740
+ }
17741
+ this.configPathStates = nextStates;
17742
+ this.recordChanges(changes);
17743
+ }
16808
17744
  scheduleFlush() {
16809
17745
  if (this.debounceTimer) {
16810
17746
  clearTimeout(this.debounceTimer);
@@ -16818,7 +17754,7 @@ var FileWatcher = class {
16818
17754
  return;
16819
17755
  }
16820
17756
  const changes = Array.from(this.pendingChanges.entries()).map(
16821
- ([path28, type]) => ({ path: path28, type })
17757
+ ([path30, type]) => ({ path: path30, type })
16822
17758
  );
16823
17759
  this.pendingChanges.clear();
16824
17760
  try {
@@ -16832,20 +17768,31 @@ var FileWatcher = class {
16832
17768
  clearTimeout(this.debounceTimer);
16833
17769
  this.debounceTimer = null;
16834
17770
  }
17771
+ if (this.nativeReconcileTimer) {
17772
+ clearTimeout(this.nativeReconcileTimer);
17773
+ this.nativeReconcileTimer = null;
17774
+ }
17775
+ this.nativeInvalidatedPaths.clear();
16835
17776
  const watcher = this.watcher;
17777
+ const nativeWatcher = this.nativeWatcher;
16836
17778
  const pendingClose = this.pendingClose;
16837
17779
  const resolveReady = this.resolveReady;
16838
17780
  this.watcher = null;
17781
+ this.nativeWatcher = null;
17782
+ this.nativeReconciler = null;
17783
+ this.nativeStarting = false;
17784
+ this.nativeInitializing = false;
17785
+ this.nativeSetupGeneration += 1;
16839
17786
  this.pendingClose = null;
16840
17787
  this.resolveReady = null;
16841
17788
  this.readyPromise = null;
16842
17789
  this.pendingChanges.clear();
16843
17790
  this.onChanges = null;
16844
- await Promise.all([watcher?.close(), pendingClose]);
17791
+ await Promise.all([watcher?.close(), nativeWatcher?.stop(), pendingClose]);
16845
17792
  resolveReady?.();
16846
17793
  }
16847
17794
  isRunning() {
16848
- return this.watcher !== null;
17795
+ return this.watcher !== null || this.nativeWatcher !== null || this.nativeStarting;
16849
17796
  }
16850
17797
  async waitUntilReady() {
16851
17798
  await (this.readyPromise ?? Promise.resolve());
@@ -16853,7 +17800,7 @@ var FileWatcher = class {
16853
17800
  };
16854
17801
 
16855
17802
  // src/watcher/git-head-watcher.ts
16856
- import * as path22 from "path";
17803
+ import * as path24 from "path";
16857
17804
  var GitHeadWatcher = class {
16858
17805
  watcher = null;
16859
17806
  projectRoot;
@@ -16875,13 +17822,13 @@ var GitHeadWatcher = class {
16875
17822
  this.readyPromise = Promise.resolve();
16876
17823
  return;
16877
17824
  }
16878
- this.readyPromise = new Promise((resolve15) => {
16879
- this.resolveReady = resolve15;
17825
+ this.readyPromise = new Promise((resolve17) => {
17826
+ this.resolveReady = resolve17;
16880
17827
  });
16881
17828
  this.onBranchChange = handler;
16882
17829
  this.currentBranch = getCurrentBranch(this.projectRoot);
16883
17830
  const headPath = getHeadPath(this.projectRoot);
16884
- const refsPath = path22.join(this.projectRoot, ".git", "refs", "heads");
17831
+ const refsPath = path24.join(this.projectRoot, ".git", "refs", "heads");
16885
17832
  this.watcher = chokidar_default.watch([headPath, refsPath], {
16886
17833
  persistent: true,
16887
17834
  ignoreInitial: true,
@@ -17532,13 +18479,19 @@ async function resolveSearchContext(input, operations) {
17532
18479
  (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
17533
18480
  );
17534
18481
  };
17535
- const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
18482
+ const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt, prioritizeSourcePaths) => {
17536
18483
  return recordAttempt(
17537
18484
  "conceptual",
17538
18485
  searchQuery,
17539
18486
  scope,
17540
18487
  relaxedFieldsForAttempt,
17541
- (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
18488
+ (trace) => operations.search(
18489
+ searchQuery,
18490
+ MAX_CONTEXT_RESULT_LIMIT,
18491
+ scope,
18492
+ input.diagnostic ? trace : void 0,
18493
+ { prioritizeSourcePaths }
18494
+ )
17542
18495
  );
17543
18496
  };
17544
18497
  const findSuccessfulAttemptState = (route) => {
@@ -17666,10 +18619,12 @@ Explicit symbol lookup only; conceptual search was not attempted.`
17666
18619
  }
17667
18620
  }
17668
18621
  for (const attempt of conceptualAttemptPlan) {
18622
+ const attemptIntent = analyzeQueryIntent(attempt.queryText);
18623
+ const prioritizeSourcePaths = attemptIntent.primary !== "docs" && attemptIntent.primary !== "test";
17669
18624
  if (inferredSymbol && attempt.queryText === inferredSymbol && attempt.queryText !== query) {
17670
18625
  decisions.fallbackFromOriginalConceptualToInferred = true;
17671
18626
  }
17672
- const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
18627
+ const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed, prioritizeSourcePaths);
17673
18628
  if (results.length > 0) {
17674
18629
  const heading = buildPackHeading("conceptual", decisions);
17675
18630
  const intent = analyzeQueryIntent(attempt.queryText);
@@ -17737,7 +18692,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17737
18692
  const directory = input.directory ?? void 0;
17738
18693
  const tokenBudget = input.tokenBudget ?? void 0;
17739
18694
  if (from && to) {
17740
- const path28 = await getCallGraphPath(
18695
+ const path30 = await getCallGraphPath(
17741
18696
  projectRoot,
17742
18697
  host,
17743
18698
  from,
@@ -17746,25 +18701,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17746
18701
  fromFilePath,
17747
18702
  toFilePath
17748
18703
  );
17749
- const pathText = formatCallGraphPathResult(path28);
17750
- if (path28.path.length > 0) {
18704
+ const pathText = formatCallGraphPathResult(path30);
18705
+ if (path30.path.length > 0) {
17751
18706
  const fitted2 = fitTextToContextBudget(
17752
18707
  pathText,
17753
18708
  tokenBudget
17754
18709
  );
17755
18710
  return {
17756
18711
  text: fitted2.text,
17757
- details: fittedDetails("path", fitted2, path28.path.length)
18712
+ details: fittedDetails("path", fitted2, path30.path.length)
17758
18713
  };
17759
18714
  }
17760
- if (path28.from.status !== "resolved" || path28.to.status !== "resolved") {
18715
+ if (path30.from.status !== "resolved" || path30.to.status !== "resolved") {
17761
18716
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
17762
18717
  return {
17763
18718
  text: fitted2.text,
17764
18719
  details: fittedDetails("path", fitted2, 0)
17765
18720
  };
17766
18721
  }
17767
- const resolvedFrom = path28.from;
18722
+ const resolvedFrom = path30.from;
17768
18723
  const { callers } = await getCallGraphData(projectRoot, host, {
17769
18724
  name: to,
17770
18725
  direction: "callers",
@@ -17806,12 +18761,13 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17806
18761
  directory: scope.directory,
17807
18762
  trace
17808
18763
  }),
17809
- search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
18764
+ search: (queryText, retrievalLimit, scope, trace, searchOptions) => searchCodebase(projectRoot, host, queryText, {
17810
18765
  limit: retrievalLimit,
17811
18766
  fileType: scope.fileType,
17812
18767
  directory: scope.directory,
17813
18768
  metadataOnly: true,
17814
- trace
18769
+ trace,
18770
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
17815
18771
  })
17816
18772
  });
17817
18773
  }
@@ -17923,7 +18879,7 @@ async function executeCallGraph(projectRoot, host, args) {
17923
18879
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
17924
18880
  }
17925
18881
  async function executeCallGraphPath(projectRoot, host, args) {
17926
- const path28 = await getCallGraphPath(
18882
+ const path30 = await getCallGraphPath(
17927
18883
  projectRoot,
17928
18884
  host,
17929
18885
  args.from,
@@ -17932,7 +18888,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
17932
18888
  args.fromFilePath,
17933
18889
  args.toFilePath
17934
18890
  );
17935
- return { text: formatCallGraphPathResult(path28) };
18891
+ return { text: formatCallGraphPathResult(path30) };
17936
18892
  }
17937
18893
  async function executeCodeCommunities(projectRoot, host, args) {
17938
18894
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -17942,11 +18898,11 @@ async function executeCodeCommunities(projectRoot, host, args) {
17942
18898
  // src/adapters/opencode/tools.ts
17943
18899
  import { writeFileSync as writeFileSync4 } from "fs";
17944
18900
  import * as os7 from "os";
17945
- import * as path25 from "path";
18901
+ import * as path27 from "path";
17946
18902
 
17947
18903
  // src/tools/visualize/activity.ts
17948
18904
  import { execFileSync } from "child_process";
17949
- import * as path23 from "path";
18905
+ import * as path25 from "path";
17950
18906
  function attachRecentActivity(data, projectRoot) {
17951
18907
  const activity = readGitActivity(projectRoot);
17952
18908
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -18108,7 +19064,7 @@ function normalizePath3(filePath) {
18108
19064
  return filePath.replace(/\\/g, "/");
18109
19065
  }
18110
19066
  function toGitRelativePath(projectRoot, filePath) {
18111
- const relativePath = path23.isAbsolute(filePath) ? path23.relative(projectRoot, filePath) : filePath;
19067
+ const relativePath = path25.isAbsolute(filePath) ? path25.relative(projectRoot, filePath) : filePath;
18112
19068
  return normalizePath3(relativePath);
18113
19069
  }
18114
19070
 
@@ -18366,7 +19322,7 @@ render();
18366
19322
  }
18367
19323
 
18368
19324
  // src/tools/visualize/transform.ts
18369
- import * as path24 from "path";
19325
+ import * as path26 from "path";
18370
19326
 
18371
19327
  // src/tools/visualize/modules.ts
18372
19328
  var MAX_MODULES = 18;
@@ -18499,8 +19455,8 @@ function compactModules(prefixToNodes) {
18499
19455
  function deriveModules(nodes) {
18500
19456
  const initial = /* @__PURE__ */ new Map();
18501
19457
  for (const node of nodes) {
18502
- const relative12 = stripToProjectRelative(node.filePath);
18503
- const prefix = modulePrefixFromRelativePath(relative12);
19458
+ const relative14 = stripToProjectRelative(node.filePath);
19459
+ const prefix = modulePrefixFromRelativePath(relative14);
18504
19460
  if (!initial.has(prefix)) initial.set(prefix, []);
18505
19461
  initial.get(prefix)?.push(node);
18506
19462
  }
@@ -18626,7 +19582,7 @@ function transformForVisualization(symbols, edges, options = {}) {
18626
19582
  filePath: s.filePath,
18627
19583
  kind: s.kind,
18628
19584
  line: s.startLine,
18629
- directory: path24.dirname(s.filePath),
19585
+ directory: path26.dirname(s.filePath),
18630
19586
  moduleId: "",
18631
19587
  moduleLabel: ""
18632
19588
  }));
@@ -18726,7 +19682,8 @@ var codebase_peek = tool({
18726
19682
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
18727
19683
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
18728
19684
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
18729
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19685
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19686
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18730
19687
  },
18731
19688
  async execute(args, context) {
18732
19689
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "peek", args.query, {
@@ -18737,7 +19694,8 @@ var codebase_peek = tool({
18737
19694
  metadataOnly: true,
18738
19695
  blameAuthor: args.blameAuthor,
18739
19696
  blameSha: args.blameSha,
18740
- blameSince: args.blameSince
19697
+ blameSince: args.blameSince,
19698
+ blameUntil: args.blameUntil
18741
19699
  }, (results) => {
18742
19700
  const text = formatCodebasePeek(results);
18743
19701
  return { output: text, text };
@@ -18799,7 +19757,9 @@ var find_similar = tool({
18799
19757
  fileType: z3.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
18800
19758
  directory: z3.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
18801
19759
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
18802
- excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
19760
+ excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)"),
19761
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19762
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18803
19763
  },
18804
19764
  async execute(args, context) {
18805
19765
  const results = await findSimilarCode(context?.worktree, DEFAULT_HOST, args.code, {
@@ -18807,7 +19767,9 @@ var find_similar = tool({
18807
19767
  fileType: args.fileType,
18808
19768
  directory: args.directory,
18809
19769
  chunkType: args.chunkType,
18810
- excludeFile: args.excludeFile
19770
+ excludeFile: args.excludeFile,
19771
+ blameSince: args.blameSince,
19772
+ blameUntil: args.blameUntil
18811
19773
  });
18812
19774
  if (results.length === 0) {
18813
19775
  return "No similar code found. Try a different snippet or run index_codebase first.";
@@ -18826,7 +19788,8 @@ var codebase_search = tool({
18826
19788
  contextLines: z3.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
18827
19789
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
18828
19790
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
18829
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19791
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19792
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
18830
19793
  },
18831
19794
  async execute(args, context) {
18832
19795
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "search", args.query, {
@@ -18837,7 +19800,8 @@ var codebase_search = tool({
18837
19800
  contextLines: args.contextLines,
18838
19801
  blameAuthor: args.blameAuthor,
18839
19802
  blameSha: args.blameSha,
18840
- blameSince: args.blameSince
19803
+ blameSince: args.blameSince,
19804
+ blameUntil: args.blameUntil
18841
19805
  }, (results) => {
18842
19806
  const text = results.length === 0 ? "No matching code found. Try a different query or run index_codebase first." : formatSearchResults(results, "score");
18843
19807
  return { output: text, text };
@@ -18946,7 +19910,7 @@ var index_visualize = tool({
18946
19910
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
18947
19911
  }
18948
19912
  const html = generateVisualizationHtml(vizData);
18949
- const outputPath = path25.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
19913
+ const outputPath = path27.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
18950
19914
  writeFileSync4(outputPath, html, "utf-8");
18951
19915
  let result = `Temporal call graph visualization generated: ${outputPath}
18952
19916
 
@@ -19050,10 +20014,16 @@ var PI_TOOL_NAMES = [
19050
20014
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
19051
20015
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
19052
20016
  ];
20017
+ var MCP_TOOL_NAMES = [
20018
+ ...PORTABLE_TOOL_NAMES,
20019
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
20020
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
20021
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE
20022
+ ];
19053
20023
 
19054
20024
  // src/commands/loader.ts
19055
20025
  import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
19056
- import * as path26 from "path";
20026
+ import * as path28 from "path";
19057
20027
  function parseFrontmatter(content) {
19058
20028
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
19059
20029
  const match = content.match(frontmatterRegex);
@@ -19079,7 +20049,7 @@ function loadCommandsFromDirectory(commandsDir) {
19079
20049
  }
19080
20050
  const files = readdirSync3(commandsDir).filter((f) => f.endsWith(".md"));
19081
20051
  for (const file of files) {
19082
- const filePath = path26.join(commandsDir, file);
20052
+ const filePath = path28.join(commandsDir, file);
19083
20053
  let content;
19084
20054
  try {
19085
20055
  content = readFileSync9(filePath, "utf-8");
@@ -19088,7 +20058,7 @@ function loadCommandsFromDirectory(commandsDir) {
19088
20058
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
19089
20059
  }
19090
20060
  const { frontmatter, body } = parseFrontmatter(content);
19091
- const name = path26.basename(file, ".md");
20061
+ const name = path28.basename(file, ".md");
19092
20062
  const description = frontmatter.description || `Run the ${name} command`;
19093
20063
  commands.set(name, {
19094
20064
  description,
@@ -19422,23 +20392,41 @@ var RoutingHintController = class {
19422
20392
 
19423
20393
  // src/adapters/opencode.ts
19424
20394
  var activeWatchers = /* @__PURE__ */ new Map();
19425
- function replaceActiveWatcher(projectRoot, nextWatcher) {
19426
- const existing = activeWatchers.get(projectRoot);
19427
- if (existing) {
19428
- existing.stop();
19429
- activeWatchers.delete(projectRoot);
19430
- }
19431
- if (nextWatcher) {
19432
- activeWatchers.set(projectRoot, nextWatcher);
20395
+ var watcherReplacementChains = /* @__PURE__ */ new Map();
20396
+ async function replaceActiveWatcher(projectRoot, createNextWatcher) {
20397
+ const chain = (watcherReplacementChains.get(projectRoot) ?? Promise.resolve()).catch(() => void 0).then(async () => {
20398
+ const existing = activeWatchers.get(projectRoot);
20399
+ if (existing) {
20400
+ try {
20401
+ await existing.stop();
20402
+ } catch (error) {
20403
+ console.error("[codebase-index] Failed to stop replaced watcher:", error);
20404
+ throw error;
20405
+ }
20406
+ if (activeWatchers.get(projectRoot) === existing) {
20407
+ activeWatchers.delete(projectRoot);
20408
+ }
20409
+ }
20410
+ if (createNextWatcher) {
20411
+ activeWatchers.set(projectRoot, createNextWatcher());
20412
+ }
20413
+ });
20414
+ watcherReplacementChains.set(projectRoot, chain);
20415
+ try {
20416
+ await chain;
20417
+ } finally {
20418
+ if (watcherReplacementChains.get(projectRoot) === chain) {
20419
+ watcherReplacementChains.delete(projectRoot);
20420
+ }
19433
20421
  }
19434
20422
  }
19435
20423
  function getCommandsDir() {
19436
20424
  let currentDir = process.cwd();
19437
20425
  if (typeof import.meta !== "undefined" && import.meta.url) {
19438
- currentDir = path27.dirname(fileURLToPath2(import.meta.url));
20426
+ currentDir = path29.dirname(fileURLToPath2(import.meta.url));
19439
20427
  }
19440
- const packageRoot = path27.basename(currentDir) === "adapters" ? path27.join(currentDir, "..", "..") : path27.join(currentDir, "..");
19441
- return path27.join(packageRoot, "commands");
20428
+ const packageRoot = path29.basename(currentDir) === "adapters" ? path29.join(currentDir, "..", "..") : path29.join(currentDir, "..");
20429
+ return path29.join(packageRoot, "commands");
19442
20430
  }
19443
20431
  function appendRoutingHints(output, hints, preferredRole) {
19444
20432
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -19479,9 +20467,12 @@ var plugin = async ({ directory, worktree }) => {
19479
20467
  startAutoIndex(projectRoot, "opencode", "startup");
19480
20468
  }
19481
20469
  if (config.indexing.watchFiles && isValidProject) {
19482
- replaceActiveWatcher(projectRoot, createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode"));
20470
+ await replaceActiveWatcher(
20471
+ projectRoot,
20472
+ () => createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode")
20473
+ );
19483
20474
  } else {
19484
- replaceActiveWatcher(projectRoot, null);
20475
+ await replaceActiveWatcher(projectRoot, null);
19485
20476
  }
19486
20477
  return {
19487
20478
  tool: {