opencode-codebase-index 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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(path30, checkUnignored, mode) {
331
+ test(path31, 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(path30);
340
+ const matched = rule[mode].test(path31);
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 = (path30, originalPath, doThrow) => {
362
- if (!isString(path30)) {
361
+ var checkPath = (path31, originalPath, doThrow) => {
362
+ if (!isString(path31)) {
363
363
  return doThrow(
364
364
  `path must be a string, but got \`${originalPath}\``,
365
365
  TypeError
366
366
  );
367
367
  }
368
- if (!path30) {
368
+ if (!path31) {
369
369
  return doThrow(`path must not be empty`, TypeError);
370
370
  }
371
- if (checkPath.isNotRelative(path30)) {
371
+ if (checkPath.isNotRelative(path31)) {
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 = (path30) => REGEX_TEST_INVALID_PATH.test(path30);
380
+ var isNotRelative = (path31) => REGEX_TEST_INVALID_PATH.test(path31);
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 path30 = originalPath && checkPath.convert(originalPath);
410
+ const path31 = originalPath && checkPath.convert(originalPath);
411
411
  checkPath(
412
- path30,
412
+ path31,
413
413
  originalPath,
414
414
  this._strictPathCheck ? throwError : RETURN_FALSE
415
415
  );
416
- return this._t(path30, cache, checkUnignored, slices);
416
+ return this._t(path31, cache, checkUnignored, slices);
417
417
  }
418
- checkIgnore(path30) {
419
- if (!REGEX_TEST_TRAILING_SLASH.test(path30)) {
420
- return this.test(path30);
418
+ checkIgnore(path31) {
419
+ if (!REGEX_TEST_TRAILING_SLASH.test(path31)) {
420
+ return this.test(path31);
421
421
  }
422
- const slices = path30.split(SLASH2).filter(Boolean);
422
+ const slices = path31.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(path30, false, MODE_CHECK_IGNORE);
435
+ return this._rules.test(path31, false, MODE_CHECK_IGNORE);
436
436
  }
437
- _t(path30, cache, checkUnignored, slices) {
438
- if (path30 in cache) {
439
- return cache[path30];
437
+ _t(path31, cache, checkUnignored, slices) {
438
+ if (path31 in cache) {
439
+ return cache[path31];
440
440
  }
441
441
  if (!slices) {
442
- slices = path30.split(SLASH2).filter(Boolean);
442
+ slices = path31.split(SLASH2).filter(Boolean);
443
443
  }
444
444
  slices.pop();
445
445
  if (!slices.length) {
446
- return cache[path30] = this._rules.test(path30, checkUnignored, MODE_IGNORE);
446
+ return cache[path31] = this._rules.test(path31, 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[path30] = parent.ignored ? parent : this._rules.test(path30, checkUnignored, MODE_IGNORE);
454
+ return cache[path31] = parent.ignored ? parent : this._rules.test(path31, checkUnignored, MODE_IGNORE);
455
455
  }
456
- ignores(path30) {
457
- return this._test(path30, this._ignoreCache, false).ignored;
456
+ ignores(path31) {
457
+ return this._test(path31, this._ignoreCache, false).ignored;
458
458
  }
459
459
  createFilter() {
460
- return (path30) => !this.ignores(path30);
460
+ return (path31) => !this.ignores(path31);
461
461
  }
462
462
  filter(paths) {
463
463
  return makeArray(paths).filter(this.createFilter());
464
464
  }
465
465
  // @returns {TestResult}
466
- test(path30) {
467
- return this._test(path30, this._testCache, true);
466
+ test(path31) {
467
+ return this._test(path31, this._testCache, true);
468
468
  }
469
469
  };
470
470
  var factory = (options) => new Ignore2(options);
471
- var isPathValid = (path30) => checkPath(path30 && checkPath.convert(path30), path30, RETURN_FALSE);
471
+ var isPathValid = (path31) => checkPath(path31 && checkPath.convert(path31), path31, 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 = (path30) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path30) || isNotRelative(path30);
476
+ checkPath.isNotRelative = (path31) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path31) || isNotRelative(path31);
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 path29 from "path";
654
+ import * as path30 from "path";
655
655
  import { fileURLToPath as fileURLToPath2 } from "url";
656
656
 
657
657
  // src/config/constants.ts
@@ -1749,8 +1749,8 @@ function loadMergedConfig(projectRoot, host) {
1749
1749
  }
1750
1750
 
1751
1751
  // src/tools/operations.ts
1752
- import { existsSync as existsSync12, realpathSync as realpathSync5, statSync as statSync5 } from "fs";
1753
- import * as path20 from "path";
1752
+ import { existsSync as existsSync13, realpathSync as realpathSync6, statSync as statSync5 } from "fs";
1753
+ import * as path21 from "path";
1754
1754
 
1755
1755
  // src/tools/knowledge-base-paths.ts
1756
1756
  import * as path8 from "path";
@@ -2599,8 +2599,8 @@ function formatExactSearchHandoff(results) {
2599
2599
  }
2600
2600
  function formatContextEvidence(result, index) {
2601
2601
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
2602
- const path30 = compactEvidenceValue(result.filePath, 120);
2603
- return `[${index}] ${result.chunkType}${symbol} in ${path30}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2602
+ const path31 = compactEvidenceValue(result.filePath, 120);
2603
+ return `[${index}] ${result.chunkType}${symbol} in ${path31}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2604
2604
  }
2605
2605
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
2606
2606
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -3242,9 +3242,9 @@ function formatEffectivenessMetrics(snapshot) {
3242
3242
  }
3243
3243
 
3244
3244
  // src/utils/auto-index.ts
3245
- import { existsSync as existsSync7, realpathSync as realpathSync3 } from "fs";
3246
- import * as os3 from "os";
3247
- import * as path11 from "path";
3245
+ import { existsSync as existsSync8, realpathSync as realpathSync4 } from "fs";
3246
+ import * as os4 from "os";
3247
+ import * as path12 from "path";
3248
3248
 
3249
3249
  // src/indexer/index-lock.ts
3250
3250
  import { randomUUID } from "crypto";
@@ -3501,7 +3501,7 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
3501
3501
  return true;
3502
3502
  }
3503
3503
  function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
3504
- const reclaimPath = path9.join(lockPath, RECLAIM_DIRECTORY_NAME);
3504
+ const reclaimPath2 = path9.join(lockPath, RECLAIM_DIRECTORY_NAME);
3505
3505
  const reclaimOwner = {
3506
3506
  pid: process.pid,
3507
3507
  hostname: os2.hostname(),
@@ -3510,19 +3510,19 @@ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
3510
3510
  expectedOwnerToken: expectedOwner.token
3511
3511
  };
3512
3512
  for (let attempt = 0; attempt < 2; attempt += 1) {
3513
- if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
3513
+ if (publishJsonDirectory(reclaimPath2, reclaimOwner)) break;
3514
3514
  if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
3515
3515
  return false;
3516
3516
  }
3517
3517
  try {
3518
- const currentReclaimer = readReclaimOwner(reclaimPath);
3518
+ const currentReclaimer = readReclaimOwner(reclaimPath2);
3519
3519
  const currentOwner = readDirectoryOwner(lockPath);
3520
3520
  if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
3521
3521
  return false;
3522
3522
  }
3523
3523
  publishRecoveryMarker(indexPath, expectedOwner);
3524
3524
  const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
3525
- const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
3525
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath2);
3526
3526
  if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
3527
3527
  return false;
3528
3528
  }
@@ -3692,10 +3692,893 @@ function completeLeaseRecovery(lease) {
3692
3692
  }
3693
3693
  }
3694
3694
 
3695
+ // src/utils/background-worker.ts
3696
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
3697
+ import {
3698
+ existsSync as existsSync6,
3699
+ lstatSync as lstatSync2,
3700
+ mkdirSync as mkdirSync2,
3701
+ readFileSync as readFileSync5,
3702
+ realpathSync as realpathSync3,
3703
+ renameSync as renameSync2,
3704
+ rmSync as rmSync2,
3705
+ writeFileSync as writeFileSync2
3706
+ } from "fs";
3707
+ import * as os3 from "os";
3708
+ import * as path10 from "path";
3709
+ var OWNER_FILE_NAME2 = "owner.json";
3710
+ var HEARTBEAT_FILE_PREFIX = "heartbeat.";
3711
+ var RECLAIM_DIRECTORY_NAME2 = "reclaim";
3712
+ var REFRESH_REQUEST_FILE_NAME = "refresh-request.json";
3713
+ var HEARTBEAT_INTERVAL_MS = 5e3;
3714
+ var STALE_LEASE_MS = 3e4;
3715
+ var RETRY_DELAY_MS = 5e3;
3716
+ var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3717
+ var BackgroundWorkerStopError = class extends Error {
3718
+ constructor(watcherError, autoIndexError) {
3719
+ super("Failed to stop background worker");
3720
+ this.watcherError = watcherError;
3721
+ this.autoIndexError = autoIndexError;
3722
+ this.name = "BackgroundWorkerStopError";
3723
+ }
3724
+ watcherError;
3725
+ autoIndexError;
3726
+ };
3727
+ var workers = /* @__PURE__ */ new Map();
3728
+ var workerKeysByProject = /* @__PURE__ */ new Map();
3729
+ var workerReplacementBarriers = /* @__PURE__ */ new Map();
3730
+ function getErrorCode2(error) {
3731
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
3732
+ }
3733
+ function canonicalizePath(targetPath) {
3734
+ const resolved = path10.resolve(targetPath);
3735
+ if (existsSync6(resolved)) {
3736
+ try {
3737
+ return realpathSync3.native(resolved);
3738
+ } catch {
3739
+ return resolved;
3740
+ }
3741
+ }
3742
+ const parent = path10.dirname(resolved);
3743
+ if (parent === resolved) return resolved;
3744
+ return path10.join(canonicalizePath(parent), path10.basename(resolved));
3745
+ }
3746
+ function projectLookupKey(projectRoot, host) {
3747
+ return `${host}::${canonicalizePath(projectRoot)}`;
3748
+ }
3749
+ function resolveIdentity(projectRoot, config, host) {
3750
+ const canonicalProjectRoot = canonicalizePath(projectRoot);
3751
+ const canonicalIndexPath = canonicalizePath(resolveProjectIndexPath(projectRoot, config.scope, host));
3752
+ return {
3753
+ canonicalIndexPath,
3754
+ canonicalProjectRoot,
3755
+ key: `${canonicalIndexPath}::${canonicalProjectRoot}`
3756
+ };
3757
+ }
3758
+ function controllerKey(identity, host) {
3759
+ return `${identity.key}::${host}`;
3760
+ }
3761
+ function leaseDirectoryName(identity) {
3762
+ const hash = createHash("sha256").update(identity.key).digest("hex").slice(0, 32);
3763
+ return `background-worker.${hash}.lease`;
3764
+ }
3765
+ function leasePathFor(identity) {
3766
+ return path10.join(identity.canonicalIndexPath, leaseDirectoryName(identity));
3767
+ }
3768
+ function parseOwner2(value) {
3769
+ if (typeof value !== "object" || value === null) return null;
3770
+ const candidate = value;
3771
+ if (candidate.version !== 1) return null;
3772
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
3773
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
3774
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3775
+ if (typeof candidate.heartbeatAt !== "string" || Number.isNaN(Date.parse(candidate.heartbeatAt))) return null;
3776
+ if (typeof candidate.projectRoot !== "string" || candidate.projectRoot.length === 0) return null;
3777
+ if (typeof candidate.indexPath !== "string" || candidate.indexPath.length === 0) return null;
3778
+ if (typeof candidate.token !== "string" || !UUID_PATTERN2.test(candidate.token)) return null;
3779
+ return candidate;
3780
+ }
3781
+ function parseHeartbeat(value, expectedToken) {
3782
+ if (typeof value !== "object" || value === null) return null;
3783
+ const candidate = value;
3784
+ if (candidate.version !== 1 || candidate.token !== expectedToken) return null;
3785
+ if (typeof candidate.heartbeatAt !== "string" || Number.isNaN(Date.parse(candidate.heartbeatAt))) return null;
3786
+ return candidate;
3787
+ }
3788
+ function parseReclaimOwner2(value) {
3789
+ if (typeof value !== "object" || value === null) return null;
3790
+ const candidate = value;
3791
+ if (candidate.version !== 1) return null;
3792
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
3793
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
3794
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3795
+ if (typeof candidate.token !== "string" || !UUID_PATTERN2.test(candidate.token)) return null;
3796
+ if (candidate.expectedOwnerToken !== null && (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN2.test(candidate.expectedOwnerToken))) return null;
3797
+ return candidate;
3798
+ }
3799
+ function heartbeatPath(leasePath, token) {
3800
+ return path10.join(leasePath, `${HEARTBEAT_FILE_PREFIX}${token}.json`);
3801
+ }
3802
+ function reclaimPath(leasePath) {
3803
+ return path10.join(leasePath, RECLAIM_DIRECTORY_NAME2);
3804
+ }
3805
+ function refreshRequestPath(leasePath) {
3806
+ return path10.join(leasePath, REFRESH_REQUEST_FILE_NAME);
3807
+ }
3808
+ function readLeaseOwner(leasePath) {
3809
+ try {
3810
+ return parseOwner2(JSON.parse(readFileSync5(path10.join(leasePath, OWNER_FILE_NAME2), "utf-8")));
3811
+ } catch {
3812
+ return null;
3813
+ }
3814
+ }
3815
+ function readOwner(leasePath) {
3816
+ const owner = readLeaseOwner(leasePath);
3817
+ if (!owner) return null;
3818
+ try {
3819
+ const heartbeat = parseHeartbeat(
3820
+ JSON.parse(readFileSync5(heartbeatPath(leasePath, owner.token), "utf-8")),
3821
+ owner.token
3822
+ );
3823
+ return heartbeat ? { ...owner, heartbeatAt: heartbeat.heartbeatAt } : owner;
3824
+ } catch {
3825
+ return owner;
3826
+ }
3827
+ }
3828
+ function readReclaimOwner2(leasePath) {
3829
+ try {
3830
+ return parseReclaimOwner2(JSON.parse(readFileSync5(path10.join(reclaimPath(leasePath), OWNER_FILE_NAME2), "utf-8")));
3831
+ } catch {
3832
+ return null;
3833
+ }
3834
+ }
3835
+ function ownerLiveness(owner) {
3836
+ if (owner.hostname !== os3.hostname()) return "unknown";
3837
+ try {
3838
+ process.kill(owner.pid, 0);
3839
+ return "alive";
3840
+ } catch (error) {
3841
+ const code = getErrorCode2(error);
3842
+ if (code === "ESRCH") return "dead";
3843
+ if (code === "EPERM") return "alive";
3844
+ return "unknown";
3845
+ }
3846
+ }
3847
+ function isHeartbeatExpired(owner) {
3848
+ return Date.now() - Date.parse(owner.heartbeatAt) >= STALE_LEASE_MS;
3849
+ }
3850
+ function sameOwner2(left, right) {
3851
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
3852
+ }
3853
+ function writeHeartbeat(leasePath, owner) {
3854
+ const targetPath = heartbeatPath(leasePath, owner.token);
3855
+ const temporaryPath = `${targetPath}.tmp.${process.pid}.${owner.token}.${randomUUID2()}`;
3856
+ const heartbeat = {
3857
+ version: 1,
3858
+ token: owner.token,
3859
+ heartbeatAt: owner.heartbeatAt
3860
+ };
3861
+ try {
3862
+ writeFileSync2(temporaryPath, JSON.stringify(heartbeat), {
3863
+ encoding: "utf-8",
3864
+ flag: "wx",
3865
+ mode: 384
3866
+ });
3867
+ renameSync2(temporaryPath, targetPath);
3868
+ const currentOwner = readLeaseOwner(leasePath);
3869
+ return currentOwner !== null && sameOwner2(currentOwner, owner);
3870
+ } finally {
3871
+ if (existsSync6(temporaryPath)) rmSync2(temporaryPath, { force: true });
3872
+ }
3873
+ }
3874
+ function requestRefreshFromLeader(leasePath, allowDisabledAutoIndex) {
3875
+ const requestPath = refreshRequestPath(leasePath);
3876
+ const temporaryPath = `${requestPath}.tmp.${process.pid}.${randomUUID2()}`;
3877
+ try {
3878
+ const request = {
3879
+ allowDisabledAutoIndex,
3880
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3881
+ version: 1
3882
+ };
3883
+ writeFileSync2(temporaryPath, JSON.stringify(request), {
3884
+ encoding: "utf-8",
3885
+ flag: "wx",
3886
+ mode: 384
3887
+ });
3888
+ renameSync2(temporaryPath, requestPath);
3889
+ } catch (error) {
3890
+ if (getErrorCode2(error) !== "ENOENT") {
3891
+ console.error("[codebase-index] Failed to request background index refresh from the project worker:", error);
3892
+ }
3893
+ } finally {
3894
+ if (existsSync6(temporaryPath)) rmSync2(temporaryPath, { force: true });
3895
+ }
3896
+ }
3897
+ function consumeRefreshRequest(leasePath) {
3898
+ const requestPath = refreshRequestPath(leasePath);
3899
+ const claimedPath = `${requestPath}.handling.${process.pid}.${randomUUID2()}`;
3900
+ try {
3901
+ renameSync2(requestPath, claimedPath);
3902
+ } catch (error) {
3903
+ if (getErrorCode2(error) === "ENOENT") return null;
3904
+ throw error;
3905
+ }
3906
+ try {
3907
+ const value = JSON.parse(readFileSync5(claimedPath, "utf-8"));
3908
+ return {
3909
+ allowDisabledAutoIndex: value.version === 1 && value.allowDisabledAutoIndex === true,
3910
+ requestedAt: typeof value.requestedAt === "string" ? value.requestedAt : (/* @__PURE__ */ new Date()).toISOString(),
3911
+ version: 1
3912
+ };
3913
+ } catch {
3914
+ return { allowDisabledAutoIndex: false, requestedAt: (/* @__PURE__ */ new Date()).toISOString(), version: 1 };
3915
+ } finally {
3916
+ rmSync2(claimedPath, { force: true });
3917
+ }
3918
+ }
3919
+ function publishLease(leasePath, owner) {
3920
+ const candidatePath = `${leasePath}.candidate.${process.pid}.${owner.token}`;
3921
+ try {
3922
+ mkdirSync2(candidatePath, { mode: 448 });
3923
+ } catch (error) {
3924
+ if (getErrorCode2(error) === "ENOENT") return false;
3925
+ throw error;
3926
+ }
3927
+ try {
3928
+ writeFileSync2(path10.join(candidatePath, OWNER_FILE_NAME2), JSON.stringify(owner), {
3929
+ encoding: "utf-8",
3930
+ flag: "wx",
3931
+ mode: 384
3932
+ });
3933
+ if (existsSync6(leasePath)) return false;
3934
+ try {
3935
+ renameSync2(candidatePath, leasePath);
3936
+ return true;
3937
+ } catch (error) {
3938
+ if (existsSync6(leasePath) || getErrorCode2(error) === "ENOENT") return false;
3939
+ throw error;
3940
+ }
3941
+ } finally {
3942
+ if (existsSync6(candidatePath)) rmSync2(candidatePath, { recursive: true, force: true });
3943
+ }
3944
+ }
3945
+ function sameReclaimOwner2(left, right) {
3946
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
3947
+ }
3948
+ function reclaimerLiveness(owner) {
3949
+ return ownerLiveness(owner);
3950
+ }
3951
+ function isReclaimMarkerExpired(leasePath, owner) {
3952
+ const startedAt = owner ? Date.parse(owner.startedAt) : (() => {
3953
+ try {
3954
+ return lstatSync2(reclaimPath(leasePath)).mtimeMs;
3955
+ } catch {
3956
+ return Date.now();
3957
+ }
3958
+ })();
3959
+ return Date.now() - startedAt >= STALE_LEASE_MS;
3960
+ }
3961
+ function hasActiveReclaimMarker(leasePath, owner) {
3962
+ const marker = readReclaimOwner2(leasePath);
3963
+ return marker !== null && marker.expectedOwnerToken === owner.token && (marker.hostname !== os3.hostname() || ownerLiveness(owner) !== "alive");
3964
+ }
3965
+ function publishReclaimMarker(leasePath, expectedOwner) {
3966
+ const markerPath = reclaimPath(leasePath);
3967
+ const owner = {
3968
+ version: 1,
3969
+ pid: process.pid,
3970
+ hostname: os3.hostname(),
3971
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3972
+ token: randomUUID2(),
3973
+ expectedOwnerToken: expectedOwner?.token ?? null
3974
+ };
3975
+ try {
3976
+ mkdirSync2(markerPath, { mode: 448 });
3977
+ } catch (error) {
3978
+ if (getErrorCode2(error) === "EEXIST" || getErrorCode2(error) === "ENOENT") return null;
3979
+ throw error;
3980
+ }
3981
+ try {
3982
+ writeFileSync2(path10.join(markerPath, OWNER_FILE_NAME2), JSON.stringify(owner), {
3983
+ encoding: "utf-8",
3984
+ flag: "wx",
3985
+ mode: 384
3986
+ });
3987
+ return owner;
3988
+ } catch (error) {
3989
+ rmSync2(markerPath, { recursive: true, force: true });
3990
+ throw error;
3991
+ }
3992
+ }
3993
+ function removeExpiredReclaimMarker(leasePath, expectedOwner) {
3994
+ const marker = readReclaimOwner2(leasePath);
3995
+ const markerPath = reclaimPath(leasePath);
3996
+ if (!existsSync6(markerPath)) return false;
3997
+ if (marker && marker.expectedOwnerToken !== (expectedOwner?.token ?? null)) return false;
3998
+ if (marker && (reclaimerLiveness(marker) === "alive" || !isReclaimMarkerExpired(leasePath, marker))) return false;
3999
+ if (!marker && !isReclaimMarkerExpired(leasePath, null)) return false;
4000
+ const staleMarkerPath = `${markerPath}.stale.${marker?.pid ?? process.pid}.${marker?.token ?? randomUUID2()}.${randomUUID2()}`;
4001
+ try {
4002
+ renameSync2(markerPath, staleMarkerPath);
4003
+ } catch (error) {
4004
+ if (getErrorCode2(error) === "ENOENT") return false;
4005
+ throw error;
4006
+ }
4007
+ try {
4008
+ let claimedMarker = null;
4009
+ try {
4010
+ claimedMarker = parseReclaimOwner2(
4011
+ JSON.parse(readFileSync5(path10.join(staleMarkerPath, OWNER_FILE_NAME2), "utf-8"))
4012
+ );
4013
+ } catch {
4014
+ claimedMarker = null;
4015
+ }
4016
+ const markerMatches = marker ? claimedMarker !== null && sameReclaimOwner2(claimedMarker, marker) : claimedMarker === null;
4017
+ if (!markerMatches || !canReclaimLease(leasePath, expectedOwner)) {
4018
+ if (!existsSync6(markerPath) && existsSync6(staleMarkerPath)) renameSync2(staleMarkerPath, markerPath);
4019
+ return false;
4020
+ }
4021
+ rmSync2(staleMarkerPath, { recursive: true, force: true });
4022
+ return true;
4023
+ } catch (error) {
4024
+ if (getErrorCode2(error) === "ENOENT") return false;
4025
+ throw error;
4026
+ }
4027
+ }
4028
+ function canReclaimLease(leasePath, expectedOwner) {
4029
+ if (!existsSync6(leasePath)) return false;
4030
+ if (!expectedOwner) return false;
4031
+ const currentOwner = readOwner(leasePath);
4032
+ if (!currentOwner || !sameOwner2(currentOwner, expectedOwner)) return false;
4033
+ if (currentOwner.hostname === os3.hostname()) {
4034
+ return ownerLiveness(currentOwner) === "dead";
4035
+ }
4036
+ return isHeartbeatExpired(currentOwner);
4037
+ }
4038
+ function reclaimLease(leasePath, expectedOwner) {
4039
+ let marker = null;
4040
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4041
+ marker = publishReclaimMarker(leasePath, expectedOwner);
4042
+ if (marker) break;
4043
+ if (attempt === 0 && removeExpiredReclaimMarker(leasePath, expectedOwner)) continue;
4044
+ return false;
4045
+ }
4046
+ if (!marker) return false;
4047
+ const markerPath = reclaimPath(leasePath);
4048
+ try {
4049
+ const currentMarker = readReclaimOwner2(leasePath);
4050
+ if (!currentMarker || !sameReclaimOwner2(currentMarker, marker) || !canReclaimLease(leasePath, expectedOwner)) {
4051
+ return false;
4052
+ }
4053
+ const stalePath = `${leasePath}.stale.${process.pid}.${marker.token}`;
4054
+ renameSync2(leasePath, stalePath);
4055
+ const quarantinedOwner = readOwner(stalePath);
4056
+ const quarantinedMarker = readReclaimOwner2(stalePath);
4057
+ if (!quarantinedMarker || !sameReclaimOwner2(quarantinedMarker, marker) || expectedOwner !== null && (!quarantinedOwner || !sameOwner2(quarantinedOwner, expectedOwner))) {
4058
+ if (!existsSync6(leasePath) && existsSync6(stalePath)) renameSync2(stalePath, leasePath);
4059
+ return false;
4060
+ }
4061
+ rmSync2(stalePath, { recursive: true, force: true });
4062
+ return true;
4063
+ } catch (error) {
4064
+ if (getErrorCode2(error) === "ENOENT") return false;
4065
+ throw error;
4066
+ } finally {
4067
+ const currentMarker = readReclaimOwner2(leasePath);
4068
+ if (currentMarker && sameReclaimOwner2(currentMarker, marker)) {
4069
+ rmSync2(markerPath, { recursive: true, force: true });
4070
+ }
4071
+ }
4072
+ }
4073
+ function acquireLease(identity) {
4074
+ mkdirSync2(identity.canonicalIndexPath, { recursive: true, mode: 448 });
4075
+ const canonicalIndexPath = realpathSync3.native(identity.canonicalIndexPath);
4076
+ const leasePath = path10.join(canonicalIndexPath, leaseDirectoryName({ ...identity, canonicalIndexPath }));
4077
+ for (let attempt = 0; attempt < 4; attempt += 1) {
4078
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
4079
+ const owner = {
4080
+ version: 1,
4081
+ pid: process.pid,
4082
+ hostname: os3.hostname(),
4083
+ startedAt: timestamp,
4084
+ heartbeatAt: timestamp,
4085
+ projectRoot: identity.canonicalProjectRoot,
4086
+ indexPath: canonicalIndexPath,
4087
+ token: randomUUID2()
4088
+ };
4089
+ if (publishLease(leasePath, owner)) {
4090
+ return { leasePath, owner };
4091
+ }
4092
+ const existingOwner = readOwner(leasePath);
4093
+ if (existingOwner) {
4094
+ if (canReclaimLease(leasePath, existingOwner) && reclaimLease(leasePath, existingOwner)) continue;
4095
+ return null;
4096
+ }
4097
+ return null;
4098
+ }
4099
+ return null;
4100
+ }
4101
+ function releaseLease(lease) {
4102
+ const currentOwner = readOwner(lease.leasePath);
4103
+ if (!currentOwner || !sameOwner2(currentOwner, lease.owner)) return false;
4104
+ const releasePath = `${lease.leasePath}.release.${lease.owner.pid}.${lease.owner.token}`;
4105
+ try {
4106
+ renameSync2(lease.leasePath, releasePath);
4107
+ } catch (error) {
4108
+ if (getErrorCode2(error) === "ENOENT") return false;
4109
+ throw error;
4110
+ }
4111
+ const claimedOwner = readOwner(releasePath);
4112
+ if (!claimedOwner || !sameOwner2(claimedOwner, lease.owner)) {
4113
+ if (!existsSync6(lease.leasePath) && existsSync6(releasePath)) {
4114
+ renameSync2(releasePath, lease.leasePath);
4115
+ }
4116
+ return false;
4117
+ }
4118
+ rmSync2(releasePath, { recursive: true, force: true });
4119
+ return true;
4120
+ }
4121
+ var BackgroundWorkerController = class {
4122
+ constructor(projectRoot, host, config, hooks, identity) {
4123
+ this.projectRoot = projectRoot;
4124
+ this.host = host;
4125
+ this.config = config;
4126
+ this.hooks = hooks;
4127
+ this.identity = identity;
4128
+ }
4129
+ projectRoot;
4130
+ host;
4131
+ config;
4132
+ hooks;
4133
+ identity;
4134
+ lease = null;
4135
+ watcher = null;
4136
+ leaderReady = Promise.resolve();
4137
+ heartbeatTimer = null;
4138
+ retryTimer = null;
4139
+ teardownRetryTimer = null;
4140
+ transition = Promise.resolve();
4141
+ stopPromise = null;
4142
+ stopped = false;
4143
+ stopping = false;
4144
+ losingLeadership = false;
4145
+ restartAfterStop = false;
4146
+ leaderWorkStopped = false;
4147
+ startingLeaderWork = false;
4148
+ stopAutoIndexOnTeardown = true;
4149
+ autoIndexStarted = false;
4150
+ reportedError = null;
4151
+ update(config, hooks, options) {
4152
+ const autoIndexWasEnabled = this.config.indexing.autoIndex;
4153
+ const shouldReplaceWatcher = this.watcher !== null && hooks.watcherFactory !== void 0 && (hooks.watcherFactory === null || hooks.replaceWatcher === true);
4154
+ this.config = config;
4155
+ this.hooks = {
4156
+ ...this.hooks,
4157
+ ...hooks,
4158
+ watcherFactory: hooks.watcherFactory === void 0 ? this.hooks.watcherFactory : hooks.watcherFactory,
4159
+ watcherFactoryForConfig: hooks.watcherFactoryForConfig === void 0 ? this.hooks.watcherFactoryForConfig : hooks.watcherFactoryForConfig
4160
+ };
4161
+ if (autoIndexWasEnabled && !config.indexing.autoIndex || options.restartAutoIndex === true && config.indexing.autoIndex && !this.startingLeaderWork) {
4162
+ this.autoIndexStarted = false;
4163
+ }
4164
+ if (!this.canRun()) {
4165
+ void this.stop().catch((error) => {
4166
+ console.error("[codebase-index] Failed to stop background worker after disabling automatic work:", error);
4167
+ });
4168
+ return;
4169
+ }
4170
+ if (shouldReplaceWatcher) {
4171
+ void this.enqueue(async () => {
4172
+ const watcher = this.watcher;
4173
+ if (watcher) {
4174
+ await watcher.stop();
4175
+ if (this.watcher === watcher) this.watcher = null;
4176
+ }
4177
+ if (this.lease && !this.stopped) this.startLeaderWork();
4178
+ }).catch((error) => {
4179
+ console.error("[codebase-index] Failed to replace background file watcher:", error);
4180
+ });
4181
+ }
4182
+ this.start();
4183
+ }
4184
+ startAfter(activation) {
4185
+ this.transition = activation.catch(() => void 0);
4186
+ this.start();
4187
+ }
4188
+ start() {
4189
+ if (!this.canRun() || this.losingLeadership) return;
4190
+ if (this.stopping) {
4191
+ this.restartAfterStop = true;
4192
+ return;
4193
+ }
4194
+ this.stopped = false;
4195
+ void this.enqueue(async () => {
4196
+ if (this.stopped || this.stopping || this.losingLeadership || !this.canRun()) return;
4197
+ if (!this.lease) {
4198
+ try {
4199
+ this.lease = acquireLease(this.identity);
4200
+ this.reportedError = null;
4201
+ } catch (error) {
4202
+ this.reportAcquireError(error);
4203
+ this.scheduleRetry();
4204
+ return;
4205
+ }
4206
+ }
4207
+ if (!this.lease) {
4208
+ this.scheduleRetry();
4209
+ return;
4210
+ }
4211
+ this.startHeartbeat();
4212
+ this.startLeaderWork();
4213
+ });
4214
+ }
4215
+ waitForStart() {
4216
+ return this.transition.catch(() => void 0).then(() => this.leaderReady);
4217
+ }
4218
+ requestRefresh(allowDisabledAutoIndex = false) {
4219
+ this.start();
4220
+ if (!this.isLeader()) {
4221
+ requestRefreshFromLeader(leasePathFor(this.identity), allowDisabledAutoIndex);
4222
+ return;
4223
+ }
4224
+ void this.enqueue(async () => {
4225
+ if (this.stopped || !this.lease) return;
4226
+ this.hooks.startAutoIndex("retrieval", allowDisabledAutoIndex);
4227
+ });
4228
+ }
4229
+ isLeader() {
4230
+ return this.lease !== null && !this.stopping && !this.losingLeadership;
4231
+ }
4232
+ isStopping() {
4233
+ return this.stopping;
4234
+ }
4235
+ getHooksForConfig(config) {
4236
+ const watcherFactoryForConfig = this.hooks.watcherFactoryForConfig;
4237
+ if (!watcherFactoryForConfig) return this.hooks;
4238
+ return {
4239
+ ...this.hooks,
4240
+ watcherFactory: watcherFactoryForConfig(config),
4241
+ replaceWatcher: true
4242
+ };
4243
+ }
4244
+ attachWatcher(watcherFactory, watcherFactoryForConfig) {
4245
+ if (this.hooks.watcherFactory !== void 0) return;
4246
+ this.hooks = {
4247
+ ...this.hooks,
4248
+ watcherFactory,
4249
+ watcherFactoryForConfig: watcherFactoryForConfig ?? this.hooks.watcherFactoryForConfig
4250
+ };
4251
+ this.start();
4252
+ }
4253
+ async stop(stopAutoIndex = true) {
4254
+ if (this.stopPromise) return this.stopPromise;
4255
+ this.stopped = true;
4256
+ this.stopping = true;
4257
+ this.stopAutoIndexOnTeardown &&= stopAutoIndex;
4258
+ this.clearRetryTimer();
4259
+ const attempt = this.enqueue(async () => {
4260
+ try {
4261
+ const lease = this.lease;
4262
+ if (this.leaderWorkStopped) {
4263
+ if (lease) {
4264
+ this.releaseStoppedLease(lease);
4265
+ } else {
4266
+ this.finishStoppedLease();
4267
+ }
4268
+ return;
4269
+ }
4270
+ const hadLeaderWork = lease !== null || this.watcher !== null || this.autoIndexStarted;
4271
+ const stopped = await this.stopLeaderWork(hadLeaderWork && this.stopAutoIndexOnTeardown);
4272
+ if (!lease) {
4273
+ this.finishStoppedLease();
4274
+ return;
4275
+ }
4276
+ if (!stopped.completed) {
4277
+ this.releaseLeaseWhenAutoIndexStops(lease, stopped.completion);
4278
+ return;
4279
+ }
4280
+ this.leaderWorkStopped = true;
4281
+ this.releaseStoppedLease(lease);
4282
+ } catch (error) {
4283
+ this.scheduleTeardownRetry();
4284
+ throw error;
4285
+ }
4286
+ });
4287
+ const completion = attempt.finally(() => {
4288
+ if (this.stopPromise === completion) this.stopPromise = null;
4289
+ });
4290
+ this.stopPromise = completion;
4291
+ return completion;
4292
+ }
4293
+ canRun() {
4294
+ return this.config.indexing.autoIndex || this.hooks.watcherFactory != null;
4295
+ }
4296
+ enqueue(operation) {
4297
+ const next = this.transition.catch(() => void 0).then(operation);
4298
+ this.transition = next;
4299
+ return next;
4300
+ }
4301
+ startLeaderWork() {
4302
+ if (this.stopped || this.stopping || this.losingLeadership) return;
4303
+ this.startingLeaderWork = true;
4304
+ try {
4305
+ if (this.config.indexing.autoIndex && !this.autoIndexStarted) {
4306
+ this.autoIndexStarted = true;
4307
+ this.hooks.startAutoIndex("startup");
4308
+ }
4309
+ if (!this.watcher && this.hooks.watcherFactory) {
4310
+ try {
4311
+ const watcher = this.hooks.watcherFactory();
4312
+ this.watcher = watcher;
4313
+ this.leaderReady = watcher.whenReady?.().catch((error) => {
4314
+ console.error("[codebase-index] Failed while waiting for background file watcher startup:", error);
4315
+ }) ?? Promise.resolve();
4316
+ } catch (error) {
4317
+ console.error("[codebase-index] Failed to start background file watcher:", error);
4318
+ this.leaderReady = Promise.resolve();
4319
+ }
4320
+ }
4321
+ } finally {
4322
+ this.startingLeaderWork = false;
4323
+ }
4324
+ }
4325
+ async stopLeaderWork(stopAutoIndex) {
4326
+ const watcher = this.watcher;
4327
+ let watcherError;
4328
+ if (watcher) {
4329
+ try {
4330
+ await watcher.stop();
4331
+ if (this.watcher === watcher) this.watcher = null;
4332
+ } catch (error) {
4333
+ watcherError = error;
4334
+ }
4335
+ }
4336
+ let autoIndexError;
4337
+ let autoIndexStop = {
4338
+ completed: true,
4339
+ completion: Promise.resolve()
4340
+ };
4341
+ if (stopAutoIndex) {
4342
+ try {
4343
+ autoIndexStop = await this.hooks.stopAutoIndex();
4344
+ this.autoIndexStarted = false;
4345
+ } catch (error) {
4346
+ autoIndexError = error;
4347
+ }
4348
+ }
4349
+ if (watcherError !== void 0 || autoIndexError !== void 0) {
4350
+ throw new BackgroundWorkerStopError(watcherError, autoIndexError);
4351
+ }
4352
+ return autoIndexStop;
4353
+ }
4354
+ releaseLeaseWhenAutoIndexStops(lease, completion) {
4355
+ void completion.then(
4356
+ () => {
4357
+ void this.enqueue(async () => {
4358
+ if (this.lease !== lease || !this.stopping) return;
4359
+ this.leaderWorkStopped = true;
4360
+ this.releaseStoppedLease(lease);
4361
+ }).catch((error) => {
4362
+ console.error("[codebase-index] Failed to release background worker lease after automatic indexing stopped:", error);
4363
+ this.scheduleTeardownRetry();
4364
+ });
4365
+ },
4366
+ (error) => {
4367
+ console.error("[codebase-index] Failed while waiting for automatic indexing to stop:", error);
4368
+ this.scheduleTeardownRetry();
4369
+ }
4370
+ );
4371
+ }
4372
+ releaseStoppedLease(lease) {
4373
+ if (this.lease !== lease) {
4374
+ this.finishStoppedLease();
4375
+ return;
4376
+ }
4377
+ releaseLease(lease);
4378
+ this.lease = null;
4379
+ this.finishStoppedLease();
4380
+ }
4381
+ finishStoppedLease() {
4382
+ this.leaderWorkStopped = false;
4383
+ this.stopAutoIndexOnTeardown = true;
4384
+ this.stopping = false;
4385
+ this.clearTimers();
4386
+ this.restartAfterTeardown();
4387
+ if (!this.stopped || this.stopping) return;
4388
+ const projectKey = projectLookupKey(this.projectRoot, this.host);
4389
+ const key = controllerKey(this.identity, this.host);
4390
+ if (workers.get(key) === this) workers.delete(key);
4391
+ if (workerKeysByProject.get(projectKey) === key) workerKeysByProject.delete(projectKey);
4392
+ }
4393
+ startHeartbeat() {
4394
+ if (this.heartbeatTimer) return;
4395
+ const heartbeat = () => {
4396
+ void this.heartbeat();
4397
+ };
4398
+ this.heartbeatTimer = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
4399
+ this.heartbeatTimer.unref?.();
4400
+ }
4401
+ async heartbeat() {
4402
+ const lease = this.lease;
4403
+ if (!lease || this.losingLeadership || this.stopped && !this.stopping) return;
4404
+ if (hasActiveReclaimMarker(lease.leasePath, lease.owner)) {
4405
+ await this.loseLeadership();
4406
+ return;
4407
+ }
4408
+ const currentOwner = readOwner(lease.leasePath);
4409
+ if (!currentOwner || !sameOwner2(currentOwner, lease.owner)) {
4410
+ await this.loseLeadership();
4411
+ return;
4412
+ }
4413
+ try {
4414
+ const nextOwner = { ...lease.owner, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() };
4415
+ if (!writeHeartbeat(lease.leasePath, nextOwner)) {
4416
+ await this.loseLeadership();
4417
+ return;
4418
+ }
4419
+ lease.owner = nextOwner;
4420
+ const refreshRequest = !this.stopping ? consumeRefreshRequest(lease.leasePath) : null;
4421
+ if (refreshRequest) {
4422
+ this.hooks.startAutoIndex("retrieval", refreshRequest.allowDisabledAutoIndex);
4423
+ }
4424
+ } catch (error) {
4425
+ const ownerAfterError = readOwner(lease.leasePath);
4426
+ if (hasActiveReclaimMarker(lease.leasePath, lease.owner) || !ownerAfterError || !sameOwner2(ownerAfterError, lease.owner)) {
4427
+ await this.loseLeadership();
4428
+ return;
4429
+ }
4430
+ console.error("[codebase-index] Failed to renew background worker lease:", error);
4431
+ }
4432
+ }
4433
+ async loseLeadership() {
4434
+ if (this.losingLeadership) return;
4435
+ this.losingLeadership = true;
4436
+ this.clearHeartbeat();
4437
+ await this.enqueue(async () => this.stopAfterLeadershipLoss());
4438
+ }
4439
+ async stopAfterLeadershipLoss() {
4440
+ const lease = this.lease;
4441
+ if (!lease) {
4442
+ this.losingLeadership = false;
4443
+ return;
4444
+ }
4445
+ try {
4446
+ const stopped = await this.stopLeaderWork(true);
4447
+ this.lease = null;
4448
+ this.losingLeadership = false;
4449
+ if (stopped.completed) {
4450
+ this.scheduleRetry();
4451
+ } else {
4452
+ void stopped.completion.then(() => this.scheduleRetry());
4453
+ }
4454
+ } catch (error) {
4455
+ console.error("[codebase-index] Failed to stop background work after losing its lease:", error);
4456
+ this.scheduleLostLeadershipTeardownRetry();
4457
+ }
4458
+ }
4459
+ scheduleRetry() {
4460
+ if (this.stopped || !this.canRun() || this.retryTimer) return;
4461
+ this.retryTimer = setTimeout(() => {
4462
+ this.retryTimer = null;
4463
+ this.start();
4464
+ }, RETRY_DELAY_MS);
4465
+ this.retryTimer.unref?.();
4466
+ }
4467
+ scheduleTeardownRetry() {
4468
+ if (!this.stopping || this.teardownRetryTimer) return;
4469
+ this.teardownRetryTimer = setTimeout(() => {
4470
+ this.teardownRetryTimer = null;
4471
+ void this.stop(this.stopAutoIndexOnTeardown).catch((error) => {
4472
+ console.error("[codebase-index] Failed to retry background worker teardown:", error);
4473
+ });
4474
+ }, RETRY_DELAY_MS);
4475
+ this.teardownRetryTimer.unref?.();
4476
+ }
4477
+ restartAfterTeardown() {
4478
+ if (!this.restartAfterStop || !this.canRun() || this.losingLeadership) return;
4479
+ this.restartAfterStop = false;
4480
+ this.stopped = false;
4481
+ this.start();
4482
+ }
4483
+ scheduleLostLeadershipTeardownRetry() {
4484
+ if (this.stopped || !this.losingLeadership || this.retryTimer) return;
4485
+ this.retryTimer = setTimeout(() => {
4486
+ this.retryTimer = null;
4487
+ void this.enqueue(async () => this.stopAfterLeadershipLoss());
4488
+ }, RETRY_DELAY_MS);
4489
+ this.retryTimer.unref?.();
4490
+ }
4491
+ clearHeartbeat() {
4492
+ if (!this.heartbeatTimer) return;
4493
+ clearInterval(this.heartbeatTimer);
4494
+ this.heartbeatTimer = null;
4495
+ }
4496
+ clearTimers() {
4497
+ this.clearHeartbeat();
4498
+ this.clearRetryTimer();
4499
+ if (this.teardownRetryTimer) {
4500
+ clearTimeout(this.teardownRetryTimer);
4501
+ this.teardownRetryTimer = null;
4502
+ }
4503
+ }
4504
+ clearRetryTimer() {
4505
+ if (!this.retryTimer) return;
4506
+ clearTimeout(this.retryTimer);
4507
+ this.retryTimer = null;
4508
+ }
4509
+ reportAcquireError(error) {
4510
+ const message = error instanceof Error ? error.message : String(error);
4511
+ if (this.reportedError === message) return;
4512
+ this.reportedError = message;
4513
+ console.error("[codebase-index] Failed to acquire background worker lease:", error);
4514
+ }
4515
+ };
4516
+ function configureBackgroundWorker(projectRoot, host, config, hooks, options = {}) {
4517
+ const projectKey = projectLookupKey(projectRoot, host);
4518
+ const identity = resolveIdentity(projectRoot, config, host);
4519
+ const key = controllerKey(identity, host);
4520
+ const previousKey = workerKeysByProject.get(projectKey);
4521
+ if (previousKey && previousKey !== key) {
4522
+ const previous = workers.get(previousKey);
4523
+ const previousBarrier = workerReplacementBarriers.get(projectKey) ?? Promise.resolve();
4524
+ const stopPrevious = previous?.stop(options.stopPreviousAutoIndex ?? true) ?? Promise.resolve();
4525
+ const activation = Promise.all([previousBarrier, stopPrevious]).then(() => void 0);
4526
+ workerReplacementBarriers.set(projectKey, activation);
4527
+ workers.delete(previousKey);
4528
+ const worker2 = new BackgroundWorkerController(projectRoot, host, config, hooks, identity);
4529
+ worker2.startAfter(activation);
4530
+ workers.set(key, worker2);
4531
+ workerKeysByProject.set(projectKey, key);
4532
+ return;
4533
+ }
4534
+ let worker = workers.get(key);
4535
+ if (!worker) {
4536
+ worker = new BackgroundWorkerController(projectRoot, host, config, hooks, identity);
4537
+ workers.set(key, worker);
4538
+ } else {
4539
+ worker.update(config, hooks, options);
4540
+ }
4541
+ workerKeysByProject.set(projectKey, key);
4542
+ worker.start();
4543
+ }
4544
+ function updateBackgroundWorkerConfig(projectRoot, host, config) {
4545
+ const projectKey = projectLookupKey(projectRoot, host);
4546
+ const key = workerKeysByProject.get(projectKey);
4547
+ const worker = key ? workers.get(key) : void 0;
4548
+ if (!worker) return;
4549
+ configureBackgroundWorker(projectRoot, host, config, worker.getHooksForConfig(config), {
4550
+ stopPreviousAutoIndex: false,
4551
+ restartAutoIndex: true
4552
+ });
4553
+ }
4554
+ function waitForBackgroundWorkerStart(projectRoot, host) {
4555
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4556
+ return workers.get(key ?? "")?.waitForStart() ?? Promise.resolve();
4557
+ }
4558
+ function requestBackgroundWorkerRefresh(projectRoot, host, allowDisabledAutoIndex = false) {
4559
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4560
+ workers.get(key ?? "")?.requestRefresh(allowDisabledAutoIndex);
4561
+ }
4562
+ function isBackgroundWorkerManaged(projectRoot, host) {
4563
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4564
+ return key !== void 0 && workers.has(key);
4565
+ }
4566
+ function isBackgroundWorkerLeader(projectRoot, host) {
4567
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4568
+ return key !== void 0 && workers.get(key)?.isLeader() === true;
4569
+ }
4570
+ async function stopBackgroundWorker(projectRoot, host) {
4571
+ const projectKey = projectLookupKey(projectRoot, host);
4572
+ const key = workerKeysByProject.get(projectKey);
4573
+ const worker = key ? workers.get(key) : void 0;
4574
+ if (!worker) return;
4575
+ await worker.stop();
4576
+ }
4577
+
3695
4578
  // src/utils/files.ts
3696
4579
  var import_ignore = __toESM(require_ignore(), 1);
3697
- import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
3698
- import * as path10 from "path";
4580
+ import { existsSync as existsSync7, readFileSync as readFileSync6, promises as fsPromises } from "fs";
4581
+ import * as path11 from "path";
3699
4582
  var PROJECT_MARKERS = [
3700
4583
  ".git",
3701
4584
  "package.json",
@@ -3713,7 +4596,7 @@ var PROJECT_MARKERS = [
3713
4596
  ];
3714
4597
  function hasProjectMarker(projectRoot) {
3715
4598
  for (const marker of PROJECT_MARKERS) {
3716
- if (existsSync6(path10.join(projectRoot, marker))) {
4599
+ if (existsSync7(path11.join(projectRoot, marker))) {
3717
4600
  return true;
3718
4601
  }
3719
4602
  }
@@ -3740,33 +4623,53 @@ function createIgnoreFilter(projectRoot) {
3740
4623
  "**/*build*/**"
3741
4624
  ];
3742
4625
  ig.add(defaultIgnores);
3743
- const gitignorePath = path10.join(projectRoot, ".gitignore");
3744
- if (existsSync6(gitignorePath)) {
3745
- const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
4626
+ const gitignorePath = path11.join(projectRoot, ".gitignore");
4627
+ if (existsSync7(gitignorePath)) {
4628
+ const gitignoreContent = readFileSync6(gitignorePath, "utf-8");
3746
4629
  ig.add(gitignoreContent);
3747
4630
  }
3748
4631
  return ig;
3749
4632
  }
3750
- function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3751
- const relativePath = path10.relative(projectRoot, filePath);
3752
- if (hasFilteredPathSegment(relativePath, path10.sep)) {
3753
- return false;
3754
- }
3755
- if (ignoreFilter.ignores(relativePath)) {
3756
- return false;
4633
+ function toPosixRelativePath(relativePath) {
4634
+ return relativePath.split(path11.sep).join("/");
4635
+ }
4636
+ function matchesAnyGlob(filePath, patterns) {
4637
+ const normalized = toPosixRelativePath(filePath);
4638
+ return patterns.some((pattern) => matchGlob(normalized, pattern));
4639
+ }
4640
+ function isExcludedByPatterns(relativePath, excludePatterns) {
4641
+ return matchesAnyGlob(relativePath, excludePatterns);
4642
+ }
4643
+ function isExcludedDirectory(relativePath, excludePatterns) {
4644
+ const normalized = toPosixRelativePath(relativePath);
4645
+ if (matchesAnyGlob(normalized, excludePatterns)) {
4646
+ return true;
3757
4647
  }
3758
4648
  for (const pattern of excludePatterns) {
3759
- if (matchGlob(relativePath, pattern)) {
3760
- return false;
4649
+ const posixPattern = toPosixRelativePath(pattern).replace(/\/+$/, "");
4650
+ if (!posixPattern.endsWith("/**")) {
4651
+ continue;
3761
4652
  }
3762
- }
3763
- for (const pattern of includePatterns) {
3764
- if (matchGlob(relativePath, pattern)) {
4653
+ const directoryPattern = posixPattern.slice(0, -3);
4654
+ if (directoryPattern && matchesAnyGlob(normalized, [directoryPattern])) {
3765
4655
  return true;
3766
4656
  }
3767
4657
  }
3768
4658
  return false;
3769
4659
  }
4660
+ function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
4661
+ const relativePath = toPosixRelativePath(path11.relative(projectRoot, filePath));
4662
+ if (hasFilteredPathSegment(relativePath, "/")) {
4663
+ return false;
4664
+ }
4665
+ if (ignoreFilter.ignores(relativePath)) {
4666
+ return false;
4667
+ }
4668
+ if (isExcludedByPatterns(relativePath, excludePatterns)) {
4669
+ return false;
4670
+ }
4671
+ return matchesAnyGlob(relativePath, includePatterns);
4672
+ }
3770
4673
  function matchGlob(filePath, pattern) {
3771
4674
  if (pattern.startsWith("**/")) {
3772
4675
  const withoutPrefix = pattern.slice(3);
@@ -3787,8 +4690,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3787
4690
  const filesInDir = [];
3788
4691
  const subdirs = [];
3789
4692
  for (const entry of entries) {
3790
- const fullPath = path10.join(dir, entry.name);
3791
- const relativePath = path10.relative(projectRoot, fullPath);
4693
+ const fullPath = path11.join(dir, entry.name);
4694
+ const relativePath = toPosixRelativePath(path11.relative(projectRoot, fullPath));
3792
4695
  if (isHiddenPathSegment(entry.name)) {
3793
4696
  if (entry.isDirectory()) {
3794
4697
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3806,6 +4709,10 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3806
4709
  continue;
3807
4710
  }
3808
4711
  if (entry.isDirectory()) {
4712
+ if (isExcludedDirectory(relativePath, excludePatterns)) {
4713
+ skipped.push({ path: relativePath, reason: "excluded" });
4714
+ continue;
4715
+ }
3809
4716
  subdirs.push({ fullPath, relativePath });
3810
4717
  } else if (entry.isFile()) {
3811
4718
  const stat5 = await fsPromises.stat(fullPath);
@@ -3813,20 +4720,11 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3813
4720
  skipped.push({ path: relativePath, reason: "too_large" });
3814
4721
  continue;
3815
4722
  }
3816
- for (const pattern of excludePatterns) {
3817
- if (matchGlob(relativePath, pattern)) {
3818
- skipped.push({ path: relativePath, reason: "excluded" });
3819
- continue;
3820
- }
3821
- }
3822
- let matched = false;
3823
- for (const pattern of includePatterns) {
3824
- if (matchGlob(relativePath, pattern)) {
3825
- matched = true;
3826
- break;
3827
- }
4723
+ if (isExcludedByPatterns(relativePath, excludePatterns)) {
4724
+ skipped.push({ path: relativePath, reason: "excluded" });
4725
+ continue;
3828
4726
  }
3829
- if (matched) {
4727
+ if (matchesAnyGlob(relativePath, includePatterns)) {
3830
4728
  filesInDir.push({ path: fullPath, size: stat5.size });
3831
4729
  }
3832
4730
  }
@@ -3837,7 +4735,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3837
4735
  yield f;
3838
4736
  }
3839
4737
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3840
- skipped.push({ path: path10.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
4738
+ skipped.push({ path: toPosixRelativePath(path11.relative(projectRoot, filesInDir[i].path)), reason: "excluded" });
3841
4739
  }
3842
4740
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3843
4741
  if (canRecurse) {
@@ -3877,8 +4775,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3877
4775
  if (additionalRoots && additionalRoots.length > 0) {
3878
4776
  const normalizedRoots = /* @__PURE__ */ new Set();
3879
4777
  for (const kbRoot of additionalRoots) {
3880
- const resolved = path10.normalize(
3881
- path10.isAbsolute(kbRoot) ? kbRoot : path10.resolve(projectRoot, kbRoot)
4778
+ const resolved = path11.normalize(
4779
+ path11.isAbsolute(kbRoot) ? kbRoot : path11.resolve(projectRoot, kbRoot)
3882
4780
  );
3883
4781
  normalizedRoots.add(resolved);
3884
4782
  }
@@ -3919,7 +4817,7 @@ function getErrorMessage(error) {
3919
4817
  return error instanceof Error ? error.message : String(error);
3920
4818
  }
3921
4819
  function runCommand(file, args, options) {
3922
- return new Promise((resolve17, reject) => {
4820
+ return new Promise((resolve18, reject) => {
3923
4821
  childProcess.execFile(
3924
4822
  file,
3925
4823
  args,
@@ -3929,7 +4827,7 @@ function runCommand(file, args, options) {
3929
4827
  reject(error);
3930
4828
  return;
3931
4829
  }
3932
- resolve17(stdout);
4830
+ resolve18(stdout);
3933
4831
  }
3934
4832
  );
3935
4833
  });
@@ -4021,29 +4919,29 @@ var AutoIndexCancelledError = class extends Error {
4021
4919
  function now() {
4022
4920
  return (/* @__PURE__ */ new Date()).toISOString();
4023
4921
  }
4024
- function canonicalizePath(targetPath) {
4025
- const resolved = path11.resolve(targetPath);
4026
- if (existsSync7(resolved)) {
4922
+ function canonicalizePath2(targetPath) {
4923
+ const resolved = path12.resolve(targetPath);
4924
+ if (existsSync8(resolved)) {
4027
4925
  try {
4028
- return realpathSync3.native(resolved);
4926
+ return realpathSync4.native(resolved);
4029
4927
  } catch {
4030
4928
  return resolved;
4031
4929
  }
4032
4930
  }
4033
- const parent = path11.dirname(resolved);
4931
+ const parent = path12.dirname(resolved);
4034
4932
  if (parent === resolved) return resolved;
4035
- return path11.join(canonicalizePath(parent), path11.basename(resolved));
4933
+ return path12.join(canonicalizePath2(parent), path12.basename(resolved));
4036
4934
  }
4037
4935
  function isHomeDirectory(projectRoot) {
4038
- return canonicalizePath(projectRoot) === canonicalizePath(os3.homedir());
4936
+ return canonicalizePath2(projectRoot) === canonicalizePath2(os4.homedir());
4039
4937
  }
4040
- function projectLookupKey(projectRoot, host) {
4041
- return `${host}::${canonicalizePath(projectRoot)}`;
4938
+ function projectLookupKey2(projectRoot, host) {
4939
+ return `${host}::${canonicalizePath2(projectRoot)}`;
4042
4940
  }
4043
4941
  function coordinatorKey(projectRoot, config, host) {
4044
- const canonicalProjectRoot = canonicalizePath(projectRoot);
4942
+ const canonicalProjectRoot = canonicalizePath2(projectRoot);
4045
4943
  const indexPath = resolveProjectIndexPath(projectRoot, config.scope, host);
4046
- return `${canonicalizePath(indexPath)}::${canonicalProjectRoot}`;
4944
+ return `${canonicalizePath2(indexPath)}::${canonicalProjectRoot}`;
4047
4945
  }
4048
4946
  function getProjectSafety(projectRoot, config) {
4049
4947
  if (isHomeDirectory(projectRoot)) {
@@ -4074,10 +4972,10 @@ function safeFailureMessage(error) {
4074
4972
  }
4075
4973
  function cancellableDelay(delayMs, signal) {
4076
4974
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
4077
- return new Promise((resolve17, reject) => {
4975
+ return new Promise((resolve18, reject) => {
4078
4976
  const timer = setTimeout(() => {
4079
4977
  signal.removeEventListener("abort", onAbort);
4080
- resolve17();
4978
+ resolve18();
4081
4979
  }, delayMs);
4082
4980
  timer.unref?.();
4083
4981
  const onAbort = () => {
@@ -4089,18 +4987,44 @@ function cancellableDelay(delayMs, signal) {
4089
4987
  }
4090
4988
  function withTimeout(promise, timeoutMs) {
4091
4989
  if (timeoutMs <= 0) return Promise.resolve(void 0);
4092
- return new Promise((resolve17) => {
4093
- const timer = setTimeout(() => resolve17(void 0), timeoutMs);
4990
+ return new Promise((resolve18) => {
4991
+ const timer = setTimeout(() => resolve18(void 0), timeoutMs);
4094
4992
  timer.unref?.();
4095
4993
  void promise.then((value) => {
4096
4994
  clearTimeout(timer);
4097
- resolve17(value);
4995
+ resolve18(value);
4098
4996
  }, () => {
4099
4997
  clearTimeout(timer);
4100
- resolve17(void 0);
4998
+ resolve18(void 0);
4101
4999
  });
4102
5000
  });
4103
5001
  }
5002
+ function settlesWithin(promise, timeoutMs) {
5003
+ if (timeoutMs <= 0) return Promise.resolve(false);
5004
+ return new Promise((resolve18) => {
5005
+ let settled = false;
5006
+ const timer = setTimeout(() => {
5007
+ if (settled) return;
5008
+ settled = true;
5009
+ resolve18(false);
5010
+ }, timeoutMs);
5011
+ timer.unref?.();
5012
+ void promise.then(
5013
+ () => {
5014
+ if (settled) return;
5015
+ settled = true;
5016
+ clearTimeout(timer);
5017
+ resolve18(true);
5018
+ },
5019
+ () => {
5020
+ if (settled) return;
5021
+ settled = true;
5022
+ clearTimeout(timer);
5023
+ resolve18(true);
5024
+ }
5025
+ );
5026
+ });
5027
+ }
4104
5028
  function requestPriority(request) {
4105
5029
  if (request.force) return 4;
4106
5030
  if (request.source === "manual") return 3;
@@ -4111,6 +5035,7 @@ function mergeRequests(current, next) {
4111
5035
  if (!current) return next;
4112
5036
  const preferred = requestPriority(next) > requestPriority(current) ? next : current;
4113
5037
  return {
5038
+ allowDisabledAutoIndex: current.allowDisabledAutoIndex || next.allowDisabledAutoIndex,
4114
5039
  checkFreshness: current.checkFreshness && next.checkFreshness,
4115
5040
  force: current.force || next.force,
4116
5041
  onProgress: next.onProgress ?? current.onProgress,
@@ -4172,11 +5097,11 @@ var AutoIndexCoordinator = class {
4172
5097
  progress: this.status.progress ? { ...this.status.progress } : void 0
4173
5098
  };
4174
5099
  }
4175
- start(source) {
5100
+ start(source, allowDisabledAutoIndex = false) {
4176
5101
  this.refreshSafety();
4177
- if (!this.registration.config.indexing.autoIndex || !this.registration.safeToRun) return null;
5102
+ if (!this.registration.config.indexing.autoIndex && !allowDisabledAutoIndex || !this.registration.safeToRun) return null;
4178
5103
  if (this.status.state === "failed") return this.inFlight;
4179
- return this.request({ checkFreshness: true, force: false, source });
5104
+ return this.request({ allowDisabledAutoIndex, checkFreshness: true, force: false, source });
4180
5105
  }
4181
5106
  request(request) {
4182
5107
  if (this.stopped) {
@@ -4251,13 +5176,15 @@ var AutoIndexCoordinator = class {
4251
5176
  retryAttempt: void 0
4252
5177
  });
4253
5178
  const inFlight = this.inFlight;
4254
- if (inFlight) {
4255
- if (waitForCompletion) {
4256
- await inFlight;
4257
- } else {
4258
- await withTimeout(inFlight, SHUTDOWN_WAIT_MS);
4259
- }
5179
+ const completion = inFlight ? inFlight.then(() => void 0, () => void 0) : Promise.resolve();
5180
+ if (!inFlight) {
5181
+ return { completed: true, completion };
5182
+ }
5183
+ if (waitForCompletion) {
5184
+ await completion;
5185
+ return { completed: true, completion };
4260
5186
  }
5187
+ return { completed: await settlesWithin(completion, SHUTDOWN_WAIT_MS), completion };
4261
5188
  }
4262
5189
  startRequest(request) {
4263
5190
  if (this.stopped || !this.canRun(request)) {
@@ -4446,7 +5373,7 @@ var AutoIndexCoordinator = class {
4446
5373
  if (request.source === "manual" || request.source === "watcher") {
4447
5374
  return true;
4448
5375
  }
4449
- return this.registration.safeToRun && this.registration.config.indexing.autoIndex;
5376
+ return this.registration.safeToRun && (this.registration.config.indexing.autoIndex || request.allowDisabledAutoIndex === true);
4450
5377
  }
4451
5378
  shouldDeferForBattery(request) {
4452
5379
  return this.registration.backgroundIndexingPolicy !== null && (request.source === "startup" || request.source === "watcher");
@@ -4479,17 +5406,17 @@ var AutoIndexCoordinator = class {
4479
5406
  }
4480
5407
  }
4481
5408
  waitForBatteryRetry(delayMs) {
4482
- return new Promise((resolve17) => {
5409
+ return new Promise((resolve18) => {
4483
5410
  const timer = setTimeout(() => {
4484
5411
  if (this.batteryRetryTimer === timer) {
4485
5412
  this.batteryRetryTimer = null;
4486
5413
  this.resolveBatteryRetry = null;
4487
5414
  }
4488
- resolve17();
5415
+ resolve18();
4489
5416
  }, delayMs);
4490
5417
  timer.unref?.();
4491
5418
  this.batteryRetryTimer = timer;
4492
- this.resolveBatteryRetry = resolve17;
5419
+ this.resolveBatteryRetry = resolve18;
4493
5420
  });
4494
5421
  }
4495
5422
  cancelBatteryRetry() {
@@ -4497,9 +5424,9 @@ var AutoIndexCoordinator = class {
4497
5424
  clearTimeout(this.batteryRetryTimer);
4498
5425
  this.batteryRetryTimer = null;
4499
5426
  }
4500
- const resolve17 = this.resolveBatteryRetry;
5427
+ const resolve18 = this.resolveBatteryRetry;
4501
5428
  this.resolveBatteryRetry = null;
4502
- resolve17?.();
5429
+ resolve18?.();
4503
5430
  }
4504
5431
  finishBatteryCheck(batteryCheck) {
4505
5432
  if (this.batteryCheck !== batteryCheck) return;
@@ -4512,12 +5439,25 @@ var AutoIndexCoordinator = class {
4512
5439
  }
4513
5440
  };
4514
5441
  function getCoordinator(projectRoot, host) {
4515
- const key = coordinatorKeysByProject.get(projectLookupKey(projectRoot, host));
5442
+ const key = coordinatorKeysByProject.get(projectLookupKey2(projectRoot, host));
4516
5443
  return key ? coordinators.get(key) ?? null : null;
4517
5444
  }
4518
- function configureAutoIndex(projectRoot, host, config, getIndexer) {
4519
- const projectKey = projectLookupKey(projectRoot, host);
5445
+ function synchronizeBackgroundWorker(projectRoot, host, config, safeToRun) {
5446
+ if (safeToRun) {
5447
+ updateBackgroundWorkerConfig(projectRoot, host, config);
5448
+ return;
5449
+ }
5450
+ void stopBackgroundWorker(projectRoot, host).catch((error) => {
5451
+ console.error("[codebase-index] Failed to stop background worker after project safety changed:", error);
5452
+ });
5453
+ }
5454
+ function configureAutoIndex(projectRoot, host, config, getIndexer, options = {}) {
5455
+ const projectKey = projectLookupKey2(projectRoot, host);
4520
5456
  const safety = getProjectSafety(projectRoot, config);
5457
+ const synchronizeWorker = options.synchronizeBackgroundWorker ?? true;
5458
+ if (options.preserveManagedWorker === true && isBackgroundWorkerManaged(projectRoot, host)) {
5459
+ return;
5460
+ }
4521
5461
  const registration = {
4522
5462
  backgroundIndexingPolicy: createBackgroundIndexingPolicy(
4523
5463
  config.indexing.pauseBackgroundIndexingOnBattery
@@ -4535,6 +5475,9 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
4535
5475
  const stopPrevious = previousCoordinator?.stop(true) ?? Promise.resolve();
4536
5476
  const activation = Promise.all([previousBarrier, stopPrevious]).then(() => void 0);
4537
5477
  coordinatorReplacementBarriers.set(projectKey, activation);
5478
+ if (synchronizeWorker) {
5479
+ synchronizeBackgroundWorker(projectRoot, host, config, safety.safeToRun);
5480
+ }
4538
5481
  coordinators.delete(previousKey);
4539
5482
  const coordinator2 = new AutoIndexCoordinator(registration);
4540
5483
  coordinator2.activateAfter(activation);
@@ -4550,11 +5493,17 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
4550
5493
  coordinator.update(registration);
4551
5494
  }
4552
5495
  coordinatorKeysByProject.set(projectKey, key);
5496
+ if (synchronizeWorker) {
5497
+ synchronizeBackgroundWorker(projectRoot, host, config, safety.safeToRun);
5498
+ }
4553
5499
  }
4554
- function startAutoIndex(projectRoot, host, source = "startup") {
4555
- return getCoordinator(projectRoot, host)?.start(source) ?? null;
5500
+ function startAutoIndexForBackgroundWorker(projectRoot, host, source = "startup", allowDisabledAutoIndex = false) {
5501
+ return getCoordinator(projectRoot, host)?.start(source, allowDisabledAutoIndex) ?? null;
4556
5502
  }
4557
5503
  function requestBackgroundIndex(projectRoot, host) {
5504
+ if (isBackgroundWorkerManaged(projectRoot, host) && !isBackgroundWorkerLeader(projectRoot, host)) {
5505
+ return null;
5506
+ }
4558
5507
  return getCoordinator(projectRoot, host)?.request({
4559
5508
  checkFreshness: false,
4560
5509
  force: false,
@@ -4594,15 +5543,23 @@ async function waitForAutoIndexForRetrieval(projectRoot, host) {
4594
5543
  };
4595
5544
  }
4596
5545
  try {
4597
- if (await hasReadableCurrentIndex(coordinator)) return { ready: true };
5546
+ const readiness = await getSearchReadiness(coordinator);
5547
+ if (readiness.searchable) {
5548
+ return { ready: true };
5549
+ }
5550
+ if (readiness.blocked) return unavailableSnapshotResult(readiness.reason);
4598
5551
  } catch {
4599
5552
  }
4600
- const job = coordinator.start("retrieval") ?? coordinator.currentJob();
5553
+ const job = startRetrievalRefresh(projectRoot, host, coordinator);
4601
5554
  if (job) {
4602
5555
  await withTimeout(job, coordinator.getWaitMs());
5556
+ } else if (isBackgroundWorkerManaged(projectRoot, host)) {
5557
+ await waitForPublishedSnapshot(coordinator, coordinator.getWaitMs());
4603
5558
  }
4604
5559
  try {
4605
- if (await hasReadableCurrentIndex(coordinator)) return { ready: true };
5560
+ const readiness = await getSearchReadiness(coordinator);
5561
+ if (readiness.searchable) return { ready: true };
5562
+ if (readiness.blocked) return unavailableSnapshotResult(readiness.reason);
4606
5563
  } catch {
4607
5564
  }
4608
5565
  const status = coordinator.snapshot();
@@ -4623,18 +5580,52 @@ async function waitForAutoIndexForRetrieval(projectRoot, host) {
4623
5580
  text: `Automatic indexing is ${status.state}. Retry shortly or call index_status for progress. You can also run index_codebase explicitly.`
4624
5581
  };
4625
5582
  }
4626
- async function hasReadableCurrentIndex(coordinator) {
5583
+ async function stopAutoIndexForBackgroundWorker(projectRoot, host, waitForCompletion = false) {
5584
+ const coordinator = getCoordinator(projectRoot, host);
5585
+ if (!coordinator) {
5586
+ return { completed: true, completion: Promise.resolve() };
5587
+ }
5588
+ return coordinator.stop(waitForCompletion);
5589
+ }
5590
+ async function getSearchReadiness(coordinator) {
4627
5591
  const indexer = coordinator.getIndexer();
4628
5592
  if (indexer.getIndexFreshness) {
4629
5593
  const freshness = await indexer.getIndexFreshness();
4630
- return freshness.readable && freshness.current;
5594
+ const searchable = freshness.readable && freshness.current && freshness.reason === "current";
5595
+ return {
5596
+ blocked: freshness.reason === "unreadable" || freshness.reason === "incompatible" || freshness.reason === "failed-batches" || freshness.reason === "migration-required",
5597
+ reason: freshness.reason,
5598
+ searchable
5599
+ };
5600
+ }
5601
+ const indexed = (await indexer.getStatus()).indexed;
5602
+ return { blocked: false, searchable: indexed };
5603
+ }
5604
+ function unavailableSnapshotResult(reason) {
5605
+ const detail = reason === "incompatible" ? "The existing index is incompatible with the configured embedding provider." : reason === "migration-required" ? "The existing index requires a storage migration." : reason === "failed-batches" ? "The existing index has failed embedding batches." : "The existing index is unreadable.";
5606
+ return {
5607
+ ready: false,
5608
+ text: `${detail} Run index_codebase before retrying retrieval.`
5609
+ };
5610
+ }
5611
+ function startRetrievalRefresh(projectRoot, host, coordinator) {
5612
+ if (isBackgroundWorkerManaged(projectRoot, host)) {
5613
+ requestBackgroundWorkerRefresh(projectRoot, host, true);
5614
+ return isBackgroundWorkerLeader(projectRoot, host) ? coordinator.currentJob() : null;
5615
+ }
5616
+ return coordinator.start("retrieval") ?? coordinator.currentJob();
5617
+ }
5618
+ async function waitForPublishedSnapshot(coordinator, waitMs) {
5619
+ const deadline = Date.now() + waitMs;
5620
+ while (Date.now() < deadline) {
5621
+ if ((await getSearchReadiness(coordinator)).searchable) return;
5622
+ await new Promise((resolve18) => setTimeout(resolve18, Math.min(250, deadline - Date.now())));
4631
5623
  }
4632
- return (await indexer.getStatus()).indexed;
4633
5624
  }
4634
5625
 
4635
5626
  // src/tools/config-state.ts
4636
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
4637
- import * as path12 from "path";
5627
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
5628
+ import * as path13 from "path";
4638
5629
  function normalizeKnowledgeBasePaths(config, projectRoot) {
4639
5630
  const normalized = { ...config };
4640
5631
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -4661,10 +5652,10 @@ function loadEditableConfig(projectRoot, host) {
4661
5652
  }
4662
5653
  function saveConfig(projectRoot, config, host) {
4663
5654
  const configPath = getConfigPath(projectRoot, host);
4664
- const configDir = path12.dirname(configPath);
4665
- const configBaseDir = path12.dirname(configDir);
4666
- if (!existsSync8(configDir)) {
4667
- mkdirSync2(configDir, { recursive: true });
5655
+ const configDir = path13.dirname(configPath);
5656
+ const configBaseDir = path13.dirname(configDir);
5657
+ if (!existsSync9(configDir)) {
5658
+ mkdirSync3(configDir, { recursive: true });
4668
5659
  }
4669
5660
  const serializableConfig = { ...config };
4670
5661
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -4672,12 +5663,12 @@ function saveConfig(projectRoot, config, host) {
4672
5663
  (kb) => serializeConfigPathValue(kb, configBaseDir)
4673
5664
  );
4674
5665
  }
4675
- writeFileSync2(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
5666
+ writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
4676
5667
  }
4677
5668
 
4678
5669
  // src/indexer/index.ts
4679
- import { existsSync as existsSync11, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4, promises as fsPromises3 } from "fs";
4680
- import * as path19 from "path";
5670
+ import { existsSync as existsSync12, readFileSync as readFileSync9, statSync as statSync4, writeFileSync as writeFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync5, promises as fsPromises3 } from "fs";
5671
+ import * as path20 from "path";
4681
5672
  import { performance as performance2 } from "perf_hooks";
4682
5673
  import { execFile as execFile5 } from "child_process";
4683
5674
  import { promisify as promisify4 } from "util";
@@ -4704,7 +5695,7 @@ function pTimeout(promise, options) {
4704
5695
  } = options;
4705
5696
  let timer;
4706
5697
  let abortHandler;
4707
- const wrappedPromise = new Promise((resolve17, reject) => {
5698
+ const wrappedPromise = new Promise((resolve18, reject) => {
4708
5699
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
4709
5700
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
4710
5701
  }
@@ -4718,7 +5709,7 @@ function pTimeout(promise, options) {
4718
5709
  };
4719
5710
  signal.addEventListener("abort", abortHandler, { once: true });
4720
5711
  }
4721
- promise.then(resolve17, reject);
5712
+ promise.then(resolve18, reject);
4722
5713
  if (milliseconds === Number.POSITIVE_INFINITY) {
4723
5714
  return;
4724
5715
  }
@@ -4726,7 +5717,7 @@ function pTimeout(promise, options) {
4726
5717
  timer = customTimers.setTimeout.call(void 0, () => {
4727
5718
  if (fallback) {
4728
5719
  try {
4729
- resolve17(fallback());
5720
+ resolve18(fallback());
4730
5721
  } catch (error) {
4731
5722
  reject(error);
4732
5723
  }
@@ -4736,7 +5727,7 @@ function pTimeout(promise, options) {
4736
5727
  promise.cancel();
4737
5728
  }
4738
5729
  if (message === false) {
4739
- resolve17();
5730
+ resolve18();
4740
5731
  } else if (message instanceof Error) {
4741
5732
  reject(message);
4742
5733
  } else {
@@ -5138,7 +6129,7 @@ var PQueue = class extends import_index.default {
5138
6129
  // Assign unique ID if not provided
5139
6130
  id: options.id ?? (this.#idAssigner++).toString()
5140
6131
  };
5141
- return new Promise((resolve17, reject) => {
6132
+ return new Promise((resolve18, reject) => {
5142
6133
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
5143
6134
  let cleanupQueueAbortHandler = () => void 0;
5144
6135
  const run = async () => {
@@ -5178,7 +6169,7 @@ var PQueue = class extends import_index.default {
5178
6169
  })]);
5179
6170
  }
5180
6171
  const result = await operation;
5181
- resolve17(result);
6172
+ resolve18(result);
5182
6173
  this.emit("completed", result);
5183
6174
  } catch (error) {
5184
6175
  reject(error);
@@ -5366,13 +6357,13 @@ var PQueue = class extends import_index.default {
5366
6357
  });
5367
6358
  }
5368
6359
  async #onEvent(event, filter) {
5369
- return new Promise((resolve17) => {
6360
+ return new Promise((resolve18) => {
5370
6361
  const listener = () => {
5371
6362
  if (filter && !filter()) {
5372
6363
  return;
5373
6364
  }
5374
6365
  this.off(event, listener);
5375
- resolve17();
6366
+ resolve18();
5376
6367
  };
5377
6368
  this.on(event, listener);
5378
6369
  });
@@ -5658,7 +6649,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5658
6649
  const finalDelay = Math.min(delayTime, remainingTime);
5659
6650
  options.signal?.throwIfAborted();
5660
6651
  if (finalDelay > 0) {
5661
- await new Promise((resolve17, reject) => {
6652
+ await new Promise((resolve18, reject) => {
5662
6653
  const onAbort = () => {
5663
6654
  clearTimeout(timeoutToken);
5664
6655
  options.signal?.removeEventListener("abort", onAbort);
@@ -5666,7 +6657,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5666
6657
  };
5667
6658
  const timeoutToken = setTimeout(() => {
5668
6659
  options.signal?.removeEventListener("abort", onAbort);
5669
- resolve17();
6660
+ resolve18();
5670
6661
  }, finalDelay);
5671
6662
  if (options.unref) {
5672
6663
  timeoutToken.unref?.();
@@ -5727,17 +6718,17 @@ async function pRetry(input, options = {}) {
5727
6718
  }
5728
6719
 
5729
6720
  // src/embeddings/detector.ts
5730
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
5731
- import * as path13 from "path";
5732
- import * as os4 from "os";
6721
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
6722
+ import * as path14 from "path";
6723
+ import * as os5 from "os";
5733
6724
  function getOpenCodeAuthPath() {
5734
- return path13.join(os4.homedir(), ".local", "share", "opencode", "auth.json");
6725
+ return path14.join(os5.homedir(), ".local", "share", "opencode", "auth.json");
5735
6726
  }
5736
6727
  function loadOpenCodeAuth() {
5737
6728
  const authPath = getOpenCodeAuthPath();
5738
6729
  try {
5739
- if (existsSync9(authPath)) {
5740
- return JSON.parse(readFileSync6(authPath, "utf-8"));
6730
+ if (existsSync10(authPath)) {
6731
+ return JSON.parse(readFileSync7(authPath, "utf-8"));
5741
6732
  }
5742
6733
  } catch {
5743
6734
  }
@@ -6028,17 +7019,17 @@ function validateExternalUrl(urlString) {
6028
7019
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6029
7020
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
6030
7021
  }
6031
- const hostname2 = parsed.hostname.toLowerCase();
6032
- if (BLOCKED_HOSTNAMES.has(hostname2)) {
6033
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
7022
+ const hostname3 = parsed.hostname.toLowerCase();
7023
+ if (BLOCKED_HOSTNAMES.has(hostname3)) {
7024
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname3})` };
6034
7025
  }
6035
7026
  for (const pattern of BLOCKED_METADATA_IPS) {
6036
- if (pattern.test(hostname2)) {
6037
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
7027
+ if (pattern.test(hostname3)) {
7028
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname3})` };
6038
7029
  }
6039
7030
  }
6040
- if (/^169\.254\./.test(hostname2)) {
6041
- return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
7031
+ if (/^169\.254\./.test(hostname3)) {
7032
+ return { valid: false, reason: `Blocked: link-local address (${hostname3})` };
6042
7033
  }
6043
7034
  return { valid: true };
6044
7035
  }
@@ -6576,6 +7567,26 @@ function formatCostEstimate(estimate) {
6576
7567
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
6577
7568
  `;
6578
7569
  }
7570
+ function formatDryRunEstimate(estimate) {
7571
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
7572
+
7573
+ Files to embed: ${estimate.filesCount.toLocaleString()}
7574
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
7575
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
7576
+
7577
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
7578
+ matches the live "Tokens used" counter only for providers that report usage on the
7579
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
7580
+ Gemini, custom) it is only an estimate.
7581
+
7582
+ For a matching provider and a project-scoped force index, the force pass clears its
7583
+ own cached embeddings, so the live counter climbs to this number. A force index on a
7584
+ shared global index can reuse cached embeddings from other projects, and an
7585
+ incremental index counts cached chunks that are not re-embedded; in both cases this
7586
+ number is an upper bound on the live counter, so a progress percent against this
7587
+ total tops out below 100%.
7588
+ `;
7589
+ }
6579
7590
  function formatBytes(bytes) {
6580
7591
  if (bytes === 0) return "0 B";
6581
7592
  const k = 1024;
@@ -7110,8 +8121,8 @@ function extractParamNames(params) {
7110
8121
  }
7111
8122
 
7112
8123
  // src/native/binding.ts
7113
- import * as os5 from "os";
7114
- import * as path14 from "path";
8124
+ import * as os6 from "os";
8125
+ import * as path15 from "path";
7115
8126
  import * as module from "module";
7116
8127
  import { fileURLToPath } from "url";
7117
8128
 
@@ -7147,7 +8158,7 @@ var MCP_BINARY_CURRENT_NAME = CURRENT_PRODUCT.mcpBinary;
7147
8158
  var STABLE_NATIVE_BINARY_NAME = IDENTITY_CATALOG.native.binaryName;
7148
8159
 
7149
8160
  // src/native/binding.ts
7150
- function getNativeBindingFilename(platform2 = os5.platform(), arch2 = os5.arch()) {
8161
+ function getNativeBindingFilename(platform2 = os6.platform(), arch2 = os6.arch()) {
7151
8162
  if (platform2 === "darwin" && arch2 === "arm64") {
7152
8163
  return `${STABLE_NATIVE_BINARY_NAME}.darwin-arm64.node`;
7153
8164
  }
@@ -7165,25 +8176,25 @@ function getNativeBindingFilename(platform2 = os5.platform(), arch2 = os5.arch()
7165
8176
  }
7166
8177
  throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
7167
8178
  }
7168
- function resolveNativeBindingPath(packageRoot, platform2 = os5.platform(), arch2 = os5.arch()) {
7169
- return path14.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
8179
+ function resolveNativeBindingPath(packageRoot, platform2 = os6.platform(), arch2 = os6.arch()) {
8180
+ return path15.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
7170
8181
  }
7171
8182
  function getNativeBinding() {
7172
8183
  let currentDir;
7173
8184
  let requireTarget;
7174
8185
  if (typeof import.meta !== "undefined" && import.meta.url) {
7175
- currentDir = path14.dirname(fileURLToPath(import.meta.url));
8186
+ currentDir = path15.dirname(fileURLToPath(import.meta.url));
7176
8187
  requireTarget = import.meta.url;
7177
8188
  } else if (typeof __dirname !== "undefined") {
7178
8189
  currentDir = __dirname;
7179
8190
  requireTarget = __filename;
7180
8191
  } else {
7181
8192
  currentDir = process.cwd();
7182
- requireTarget = path14.join(currentDir, "index.js");
8193
+ requireTarget = path15.join(currentDir, "index.js");
7183
8194
  }
7184
8195
  const normalizedDir = currentDir.replace(/\\/g, "/");
7185
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path14.join("src", "native"));
7186
- const packageRoot = isDevMode ? path14.resolve(currentDir, "../..") : path14.resolve(currentDir, "..");
8196
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path15.join("src", "native"));
8197
+ const packageRoot = isDevMode ? path15.resolve(currentDir, "../..") : path15.resolve(currentDir, "..");
7187
8198
  const nativePath = resolveNativeBindingPath(packageRoot);
7188
8199
  const require2 = module.createRequire(requireTarget);
7189
8200
  return require2(nativePath);
@@ -7767,8 +8778,8 @@ var Database = class _Database {
7767
8778
 
7768
8779
  // src/git/branch-materialization.ts
7769
8780
  import { promises as fsPromises2 } from "fs";
7770
- import * as os6 from "os";
7771
- import * as path15 from "path";
8781
+ import * as os7 from "os";
8782
+ import * as path16 from "path";
7772
8783
 
7773
8784
  // src/git/branch-resolution.ts
7774
8785
  import { execFile as execFile2 } from "child_process";
@@ -8087,13 +9098,13 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
8087
9098
  return false;
8088
9099
  }
8089
9100
  function isPathWithinRoot(filePath, rootPath) {
8090
- const relative14 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8091
- return relative14 === "" || !relative14.startsWith(`..${path15.sep}`) && relative14 !== ".." && !path15.isAbsolute(relative14);
9101
+ const relative14 = path16.relative(path16.resolve(rootPath), path16.resolve(filePath));
9102
+ return relative14 === "" || !relative14.startsWith(`..${path16.sep}`) && relative14 !== ".." && !path16.isAbsolute(relative14);
8092
9103
  }
8093
9104
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
8094
9105
  if (await pathExists(worktreePath)) return false;
8095
9106
  const commonDir = await runGit(projectRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
8096
- const registrationsRoot = path15.join(commonDir, "worktrees");
9107
+ const registrationsRoot = path16.join(commonDir, "worktrees");
8097
9108
  let entries;
8098
9109
  try {
8099
9110
  entries = await fsPromises2.readdir(registrationsRoot, { withFileTypes: true });
@@ -8104,16 +9115,16 @@ async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath)
8104
9115
  const target = canonicalizePathForComparison(worktreePath);
8105
9116
  for (const entry of entries) {
8106
9117
  if (!entry.isDirectory()) continue;
8107
- const registrationPath = path15.join(registrationsRoot, entry.name);
9118
+ const registrationPath = path16.join(registrationsRoot, entry.name);
8108
9119
  if (!isPathWithinRoot(registrationPath, registrationsRoot)) continue;
8109
9120
  let gitdirPath;
8110
9121
  try {
8111
- gitdirPath = (await fsPromises2.readFile(path15.join(registrationPath, "gitdir"), "utf8")).trim();
9122
+ gitdirPath = (await fsPromises2.readFile(path16.join(registrationPath, "gitdir"), "utf8")).trim();
8112
9123
  } catch {
8113
9124
  continue;
8114
9125
  }
8115
- const resolvedGitdirPath = path15.isAbsolute(gitdirPath) ? gitdirPath : path15.resolve(registrationPath, gitdirPath);
8116
- if (canonicalizePathForComparison(path15.dirname(resolvedGitdirPath)) !== target) continue;
9126
+ const resolvedGitdirPath = path16.isAbsolute(gitdirPath) ? gitdirPath : path16.resolve(registrationPath, gitdirPath);
9127
+ if (canonicalizePathForComparison(path16.dirname(resolvedGitdirPath)) !== target) continue;
8117
9128
  await fsPromises2.rm(registrationPath, { recursive: true, force: true });
8118
9129
  return true;
8119
9130
  }
@@ -8131,7 +9142,7 @@ async function removeWorktree(projectRoot, worktreePath) {
8131
9142
  registered = await isWorktreeRegistered(projectRoot, worktreePath);
8132
9143
  } catch (error) {
8133
9144
  errors.push(asError(error));
8134
- throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path15.dirname(worktreePath)}`);
9145
+ throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path16.dirname(worktreePath)}`);
8135
9146
  }
8136
9147
  if (registered) {
8137
9148
  try {
@@ -8147,7 +9158,7 @@ async function removeWorktree(projectRoot, worktreePath) {
8147
9158
  registered = await isWorktreeRegistered(projectRoot, worktreePath);
8148
9159
  } catch (error) {
8149
9160
  errors.push(asError(error));
8150
- throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path15.dirname(worktreePath)}`);
9161
+ throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path16.dirname(worktreePath)}`);
8151
9162
  }
8152
9163
  }
8153
9164
  if (registered && !await pathExists(worktreePath)) {
@@ -8160,13 +9171,13 @@ async function removeWorktree(projectRoot, worktreePath) {
8160
9171
  }
8161
9172
  if (registered) {
8162
9173
  errors.push(new Error(`Temporary worktree remains registered: ${worktreePath}`));
8163
- throw new AggregateError(errors, `Failed to deregister temporary worktree; preserved ${path15.dirname(worktreePath)}`);
9174
+ throw new AggregateError(errors, `Failed to deregister temporary worktree; preserved ${path16.dirname(worktreePath)}`);
8164
9175
  }
8165
9176
  try {
8166
- await fsPromises2.rm(path15.dirname(worktreePath), { recursive: true, force: true });
9177
+ await fsPromises2.rm(path16.dirname(worktreePath), { recursive: true, force: true });
8167
9178
  } catch (error) {
8168
9179
  errors.push(asError(error));
8169
- throw new AggregateError(errors, `Deregistered the temporary worktree but could not remove ${path15.dirname(worktreePath)}`);
9180
+ throw new AggregateError(errors, `Deregistered the temporary worktree but could not remove ${path16.dirname(worktreePath)}`);
8170
9181
  }
8171
9182
  }
8172
9183
  async function cleanupTemporaryWorktree(projectRoot, worktreePath, temporaryRoot) {
@@ -8202,9 +9213,9 @@ async function withMaterializedBranch(request, callback) {
8202
9213
  `Git ref ${JSON.stringify(request.ref ?? request.branch)} is not available locally. For an unfetched branch, pass a remote-qualified name such as origin/feature.`
8203
9214
  );
8204
9215
  }
8205
- const temporaryRoot = await fsPromises2.mkdtemp(path15.join(os6.tmpdir(), "codebase-index-branch-"));
8206
- const worktreePath = path15.join(temporaryRoot, "worktree");
8207
- const hooksPath = path15.join(temporaryRoot, "hooks");
9216
+ const temporaryRoot = await fsPromises2.mkdtemp(path16.join(os7.tmpdir(), "codebase-index-branch-"));
9217
+ const worktreePath = path16.join(temporaryRoot, "worktree");
9218
+ const hooksPath = path16.join(temporaryRoot, "hooks");
8208
9219
  await fsPromises2.mkdir(hooksPath);
8209
9220
  const info = {
8210
9221
  branch: request.branch,
@@ -8255,8 +9266,8 @@ async function withMaterializedBranch(request, callback) {
8255
9266
 
8256
9267
  // src/tools/changed-files.ts
8257
9268
  import { execFile as execFile3 } from "child_process";
8258
- import { realpathSync as realpathSync4 } from "fs";
8259
- import * as path16 from "path";
9269
+ import { realpathSync as realpathSync5 } from "fs";
9270
+ import * as path17 from "path";
8260
9271
  import { promisify as promisify2 } from "util";
8261
9272
  var execFileAsync2 = promisify2(execFile3);
8262
9273
  var GH_PR_VIEW_FIELDS = [
@@ -8398,9 +9409,9 @@ function getHeadRepositoryIdentity(data, host) {
8398
9409
  return `${host}/${owner.toLowerCase()}/${repository.replace(/\.git$/i, "").toLowerCase()}`;
8399
9410
  }
8400
9411
  function getLocalRepositoryIdentity(projectRoot) {
8401
- let canonicalRoot = path16.resolve(projectRoot);
9412
+ let canonicalRoot = path17.resolve(projectRoot);
8402
9413
  try {
8403
- canonicalRoot = realpathSync4.native(canonicalRoot);
9414
+ canonicalRoot = realpathSync5.native(canonicalRoot);
8404
9415
  } catch {
8405
9416
  }
8406
9417
  return `local:${canonicalRoot}`;
@@ -8459,17 +9470,17 @@ async function getMergeBase(projectRoot, baseCommit, headCommit) {
8459
9470
  return commit;
8460
9471
  }
8461
9472
  function normalizeFiles(rawFiles, projectRoot) {
8462
- const root = path16.resolve(projectRoot);
9473
+ const root = path17.resolve(projectRoot);
8463
9474
  const seen = /* @__PURE__ */ new Set();
8464
9475
  const result = [];
8465
9476
  for (const raw of rawFiles) {
8466
9477
  if (raw.length === 0) continue;
8467
- const absolute = path16.resolve(root, raw);
8468
- const relative14 = path16.relative(root, absolute);
8469
- if (path16.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative14)) {
9478
+ const absolute = path17.resolve(root, raw);
9479
+ const relative14 = path17.relative(root, absolute);
9480
+ if (path17.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative14)) {
8470
9481
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
8471
9482
  }
8472
- const cleaned = relative14.startsWith(`.${path16.sep}`) ? relative14.slice(2) : relative14;
9483
+ const cleaned = relative14.startsWith(`.${path17.sep}`) ? relative14.slice(2) : relative14;
8473
9484
  if (!seen.has(cleaned)) {
8474
9485
  seen.add(cleaned);
8475
9486
  result.push(cleaned);
@@ -8480,7 +9491,7 @@ function normalizeFiles(rawFiles, projectRoot) {
8480
9491
 
8481
9492
  // src/indexer/git-blame.ts
8482
9493
  import { execFile as execFile4 } from "child_process";
8483
- import * as path17 from "path";
9494
+ import * as path18 from "path";
8484
9495
  import { promisify as promisify3 } from "util";
8485
9496
  var execFileAsync3 = promisify3(execFile4);
8486
9497
  function parseGitBlamePorcelain(output) {
@@ -8518,7 +9529,7 @@ function parseGitBlamePorcelain(output) {
8518
9529
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
8519
9530
  }
8520
9531
  async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
8521
- const relativePath = path17.relative(projectRoot, filePath);
9532
+ const relativePath = path18.relative(projectRoot, filePath);
8522
9533
  try {
8523
9534
  const { stdout } = await execFileAsync3(
8524
9535
  "git",
@@ -8850,6 +9861,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8850
9861
  "enum_declaration",
8851
9862
  "function_definition",
8852
9863
  "class_definition",
9864
+ // Ruby module/class symbols that are declaration-bearing and navigable.
9865
+ "class",
9866
+ "module",
8853
9867
  "class_specifier",
8854
9868
  "struct_specifier",
8855
9869
  "namespace_definition",
@@ -9094,8 +10108,8 @@ function pathSegmentsForAffinityMatch(filePath) {
9094
10108
  if (segments.length === 0) {
9095
10109
  return [];
9096
10110
  }
9097
- const basename9 = segments[segments.length - 1] ?? "";
9098
- const basenameWithoutExt = basename9.replace(/\.[^/.]+$/u, "");
10111
+ const basename10 = segments[segments.length - 1] ?? "";
10112
+ const basenameWithoutExt = basename10.replace(/\.[^/.]+$/u, "");
9099
10113
  const normalizedSegments = segments.map((segment) => segment.toLowerCase());
9100
10114
  return Array.from(/* @__PURE__ */ new Set([
9101
10115
  ...normalizedSegments,
@@ -9404,8 +10418,8 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
9404
10418
 
9405
10419
  // src/indexer/failed-state-persistence.ts
9406
10420
  import * as fs2 from "fs";
9407
- import { createHash, randomBytes as randomBytes3 } from "crypto";
9408
- import * as path18 from "path";
10421
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
10422
+ import * as path19 from "path";
9409
10423
  import { StringDecoder } from "string_decoder";
9410
10424
  var CURRENT_FAILED_BATCH_VERSION = 1;
9411
10425
  var DEFAULT_MALFORMED_LINE_ACTION = "skip";
@@ -9423,7 +10437,7 @@ function* readFailedBatchRecords(filePath, options = {}) {
9423
10437
  function createFailedBatchWriter(targetPath) {
9424
10438
  const temporaryPath = createTemporaryPath(targetPath);
9425
10439
  let finalized = false;
9426
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10440
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9427
10441
  fs2.closeSync(fs2.openSync(temporaryPath, "w"));
9428
10442
  const write = (record) => {
9429
10443
  if (finalized) {
@@ -9442,7 +10456,7 @@ function createFailedBatchWriter(targetPath) {
9442
10456
  if (lines.length === 0) {
9443
10457
  return;
9444
10458
  }
9445
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10459
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9446
10460
  fs2.appendFileSync(temporaryPath, `${lines.join("\n")}
9447
10461
  `, "utf-8");
9448
10462
  };
@@ -9450,7 +10464,7 @@ function createFailedBatchWriter(targetPath) {
9450
10464
  if (finalized) {
9451
10465
  return;
9452
10466
  }
9453
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10467
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9454
10468
  fs2.renameSync(temporaryPath, targetPath);
9455
10469
  finalized = true;
9456
10470
  };
@@ -9596,10 +10610,10 @@ function stripLeadingBomAndWhitespace(value) {
9596
10610
  return result;
9597
10611
  }
9598
10612
  function createTemporaryPath(targetPath) {
9599
- const randomId = createHash("sha1").update(`${Date.now()}:${randomBytes3(8).toString("hex")}`).digest("hex");
9600
- const targetDir = path18.dirname(targetPath);
9601
- const baseName = path18.basename(targetPath);
9602
- return path18.join(targetDir, `.${baseName}.${randomId}.tmp`);
10613
+ const randomId = createHash2("sha1").update(`${Date.now()}:${randomBytes3(8).toString("hex")}`).digest("hex");
10614
+ const targetDir = path19.dirname(targetPath);
10615
+ const baseName = path19.basename(targetPath);
10616
+ return path19.join(targetDir, `.${baseName}.${randomId}.tmp`);
9603
10617
  }
9604
10618
  function handleMalformedLine(filePath, lineNumber, line, error, options) {
9605
10619
  const action = options.malformedLineAction ?? DEFAULT_MALFORMED_LINE_ACTION;
@@ -9845,9 +10859,9 @@ var SWIFT_PARSER_VERSION = "1";
9845
10859
  var METAL_PARSER_VERSION = "1";
9846
10860
  var SYMBOL_EXTRACTOR_VERSION = "1";
9847
10861
  function isPathWithinRoot2(filePath, rootPath) {
9848
- const normalizedFilePath = path19.resolve(filePath);
9849
- const normalizedRoot = path19.resolve(rootPath);
9850
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path19.sep}`);
10862
+ const normalizedFilePath = path20.resolve(filePath);
10863
+ const normalizedRoot = path20.resolve(rootPath);
10864
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path20.sep}`);
9851
10865
  }
9852
10866
  function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9853
10867
  if (combined.length === 0) {
@@ -10178,10 +11192,10 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
10178
11192
  }
10179
11193
  if (options?.directory) {
10180
11194
  const candidatePath = canonicalizePathForComparison(
10181
- path19.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path19.sep))
11195
+ path20.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path20.sep))
10182
11196
  );
10183
11197
  const directoryPath = canonicalizePathForComparison(
10184
- path19.resolve(projectRoot, options.directory.trim().replace(/\\/g, path19.sep))
11198
+ path20.resolve(projectRoot, options.directory.trim().replace(/\\/g, path20.sep))
10185
11199
  );
10186
11200
  if (!isPathWithinRoot2(candidatePath, directoryPath)) return false;
10187
11201
  }
@@ -10294,26 +11308,37 @@ var Indexer = class _Indexer {
10294
11308
  return this.indexPathOverride ?? resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
10295
11309
  }
10296
11310
  toCanonicalFilePath(filePath) {
10297
- if (!path19.isAbsolute(filePath)) {
11311
+ if (!path20.isAbsolute(filePath)) {
10298
11312
  return this.resolveStoredFilePath(filePath, this.projectRoot);
10299
11313
  }
10300
- if (path19.resolve(this.materializedProjectRoot) === path19.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
11314
+ if (path20.resolve(this.materializedProjectRoot) === path20.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
10301
11315
  return filePath;
10302
11316
  }
10303
- return path19.resolve(this.projectRoot, path19.relative(this.materializedProjectRoot, filePath));
11317
+ return path20.resolve(this.projectRoot, path20.relative(this.materializedProjectRoot, filePath));
10304
11318
  }
10305
11319
  toStoredFilePath(filePath) {
10306
11320
  const canonicalFilePath = this.toCanonicalFilePath(filePath);
10307
11321
  if (this.config.scope !== "project" || !isPathWithinRoot2(canonicalFilePath, this.projectRoot)) {
10308
11322
  return canonicalFilePath;
10309
11323
  }
10310
- return path19.relative(this.projectRoot, canonicalFilePath).split(path19.sep).join("/");
11324
+ return path20.relative(this.projectRoot, canonicalFilePath).split(path20.sep).join("/");
11325
+ }
11326
+ isStoredPathExcluded(storedPath) {
11327
+ let matchPath = storedPath.split(path20.sep).join("/");
11328
+ if (path20.isAbsolute(storedPath)) {
11329
+ const relativePath = path20.relative(this.projectRoot, storedPath).split(path20.sep).join("/");
11330
+ if (relativePath.startsWith("..") || path20.isAbsolute(relativePath)) {
11331
+ return false;
11332
+ }
11333
+ matchPath = relativePath;
11334
+ }
11335
+ return isExcludedByPatterns(matchPath, this.config.exclude);
10311
11336
  }
10312
11337
  resolveStoredFilePath(filePath, rootPath = this.projectRoot) {
10313
- if (path19.isAbsolute(filePath)) {
11338
+ if (path20.isAbsolute(filePath)) {
10314
11339
  return filePath;
10315
11340
  }
10316
- const resolvedPath = path19.resolve(rootPath, ...filePath.split("/"));
11341
+ const resolvedPath = path20.resolve(rootPath, ...filePath.split("/"));
10317
11342
  if (!isPathWithinRoot2(resolvedPath, rootPath)) {
10318
11343
  throw new Error(`Stored project path escapes project root: ${JSON.stringify(filePath)}`);
10319
11344
  }
@@ -10337,7 +11362,7 @@ var Indexer = class _Indexer {
10337
11362
  }
10338
11363
  toMaterializedFilePath(filePath) {
10339
11364
  const storedFilePath = this.toStoredFilePath(filePath);
10340
- if (path19.isAbsolute(storedFilePath)) {
11365
+ if (path20.isAbsolute(storedFilePath)) {
10341
11366
  return storedFilePath;
10342
11367
  }
10343
11368
  return this.resolveStoredFilePath(storedFilePath, this.materializedProjectRoot);
@@ -10354,10 +11379,10 @@ var Indexer = class _Indexer {
10354
11379
  }
10355
11380
  getRuntimeArtifactPath(fileName) {
10356
11381
  const namespace = this.getRuntimeArtifactNamespace();
10357
- if (!namespace) return path19.join(this.indexPath, fileName);
10358
- const extension = path19.extname(fileName);
11382
+ if (!namespace) return path20.join(this.indexPath, fileName);
11383
+ const extension = path20.extname(fileName);
10359
11384
  const baseName = fileName.slice(0, fileName.length - extension.length);
10360
- return path19.join(this.indexPath, `${baseName}.${namespace}${extension}`);
11385
+ return path20.join(this.indexPath, `${baseName}.${namespace}${extension}`);
10361
11386
  }
10362
11387
  refreshRuntimeArtifactPaths() {
10363
11388
  this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
@@ -10370,14 +11395,14 @@ var Indexer = class _Indexer {
10370
11395
  getMaterializedKnowledgeBases() {
10371
11396
  const canonicalProjectRoot = this.getCanonicalPath(this.projectRoot);
10372
11397
  return this.config.knowledgeBases.map((knowledgeBase) => {
10373
- const configuredPath = path19.isAbsolute(knowledgeBase) ? knowledgeBase : path19.resolve(this.projectRoot, knowledgeBase);
11398
+ const configuredPath = path20.isAbsolute(knowledgeBase) ? knowledgeBase : path20.resolve(this.projectRoot, knowledgeBase);
10374
11399
  const canonicalPath = this.getCanonicalPath(configuredPath);
10375
11400
  if (!isPathWithinRoot2(canonicalPath, canonicalProjectRoot)) {
10376
11401
  return canonicalPath;
10377
11402
  }
10378
- return path19.resolve(
11403
+ return path20.resolve(
10379
11404
  this.materializedProjectRoot,
10380
- path19.relative(canonicalProjectRoot, canonicalPath)
11405
+ path20.relative(canonicalProjectRoot, canonicalPath)
10381
11406
  );
10382
11407
  });
10383
11408
  }
@@ -10385,7 +11410,7 @@ var Indexer = class _Indexer {
10385
11410
  try {
10386
11411
  return canonicalizePathForComparison(targetPath);
10387
11412
  } catch {
10388
- return path19.resolve(targetPath);
11413
+ return path20.resolve(targetPath);
10389
11414
  }
10390
11415
  }
10391
11416
  getProjectIdentityHash(projectRoot) {
@@ -10466,7 +11491,7 @@ var Indexer = class _Indexer {
10466
11491
  } catch (error) {
10467
11492
  releaseError = error;
10468
11493
  this.writerArtifactFingerprint = null;
10469
- if (!existsSync11(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
11494
+ if (!existsSync12(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
10470
11495
  this.activeIndexLease = null;
10471
11496
  }
10472
11497
  }
@@ -10484,12 +11509,12 @@ var Indexer = class _Indexer {
10484
11509
  return this.activeIndexLease;
10485
11510
  }
10486
11511
  loadFileHashCache() {
10487
- if (!existsSync11(this.fileHashCachePath)) {
11512
+ if (!existsSync12(this.fileHashCachePath)) {
10488
11513
  this.fileHashCache = /* @__PURE__ */ new Map();
10489
11514
  return;
10490
11515
  }
10491
11516
  try {
10492
- const data = readFileSync8(this.fileHashCachePath, "utf-8");
11517
+ const data = readFileSync9(this.fileHashCachePath, "utf-8");
10493
11518
  const parsed = JSON.parse(data);
10494
11519
  this.fileHashCache = new Map(Object.entries(parsed));
10495
11520
  } catch (error) {
@@ -10511,24 +11536,24 @@ var Indexer = class _Indexer {
10511
11536
  atomicWriteSync(targetPath, data) {
10512
11537
  const lease = this.requireActiveLease();
10513
11538
  const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
10514
- mkdirSync4(path19.dirname(targetPath), { recursive: true });
11539
+ mkdirSync5(path20.dirname(targetPath), { recursive: true });
10515
11540
  try {
10516
- writeFileSync3(tempPath, data);
10517
- renameSync3(tempPath, targetPath);
11541
+ writeFileSync4(tempPath, data);
11542
+ renameSync4(tempPath, targetPath);
10518
11543
  } finally {
10519
11544
  removeLeaseTemporaryPath(tempPath);
10520
11545
  }
10521
11546
  }
10522
11547
  saveInvertedIndex(invertedIndex) {
10523
11548
  this.atomicWriteSync(
10524
- path19.join(this.indexPath, "inverted-index.json"),
11549
+ path20.join(this.indexPath, "inverted-index.json"),
10525
11550
  invertedIndex.serialize()
10526
11551
  );
10527
11552
  }
10528
11553
  getScopedRoots(projectRoot = this.projectRoot) {
10529
11554
  const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
10530
11555
  for (const kbRoot of this.config.knowledgeBases) {
10531
- roots.add(this.getCanonicalPath(path19.resolve(projectRoot, kbRoot)));
11556
+ roots.add(this.getCanonicalPath(path20.resolve(projectRoot, kbRoot)));
10532
11557
  }
10533
11558
  return Array.from(roots);
10534
11559
  }
@@ -10945,7 +11970,7 @@ var Indexer = class _Indexer {
10945
11970
  return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
10946
11971
  }
10947
11972
  hasUnknownLegacyForceIndexClear(owner) {
10948
- return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && existsSync11(path19.join(this.indexPath, "force-index-phase"));
11973
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && existsSync12(path20.join(this.indexPath, "force-index-phase"));
10949
11974
  }
10950
11975
  async recoverFromInterruptedIndexingUnlocked(owners) {
10951
11976
  for (const owner of owners) {
@@ -11131,7 +12156,7 @@ var Indexer = class _Indexer {
11131
12156
  }
11132
12157
  }
11133
12158
  clearFailedBatchState() {
11134
- if (existsSync11(this.failedBatchesPath)) {
12159
+ if (existsSync12(this.failedBatchesPath)) {
11135
12160
  try {
11136
12161
  unlinkSync2(this.failedBatchesPath);
11137
12162
  } catch {
@@ -11333,7 +12358,7 @@ var Indexer = class _Indexer {
11333
12358
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
11334
12359
  const task = options.queue.add(async () => {
11335
12360
  if (options.rateLimitState.backoffMs > 0) {
11336
- await new Promise((resolve17) => setTimeout(resolve17, options.rateLimitState.backoffMs));
12361
+ await new Promise((resolve18) => setTimeout(resolve18, options.rateLimitState.backoffMs));
11337
12362
  }
11338
12363
  try {
11339
12364
  const embeddingResult = await pRetry(
@@ -11752,12 +12777,12 @@ var Indexer = class _Indexer {
11752
12777
  }
11753
12778
  }
11754
12779
  captureReaderArtifactFingerprint() {
11755
- const storePath = path19.join(this.indexPath, "vectors");
12780
+ const storePath = path20.join(this.indexPath, "vectors");
11756
12781
  return {
11757
12782
  vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
11758
- keyword: this.getReaderFileFingerprint(path19.join(this.indexPath, "inverted-index.json")),
11759
- database: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db")),
11760
- databaseIdentity: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db"), true)
12783
+ keyword: this.getReaderFileFingerprint(path20.join(this.indexPath, "inverted-index.json")),
12784
+ database: this.getReaderFileFingerprint(path20.join(this.indexPath, "codebase.db")),
12785
+ databaseIdentity: this.getReaderFileFingerprint(path20.join(this.indexPath, "codebase.db"), true)
11761
12786
  };
11762
12787
  }
11763
12788
  refreshReaderArtifacts() {
@@ -11782,13 +12807,13 @@ var Indexer = class _Indexer {
11782
12807
  issues.set(component, this.createReadIssue(component, message));
11783
12808
  this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
11784
12809
  };
11785
- const storePath = path19.join(this.indexPath, "vectors");
12810
+ const storePath = path20.join(this.indexPath, "vectors");
11786
12811
  const vectorMetadataPath = `${storePath}.meta.json`;
11787
- const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11788
- const dbPath = path19.join(this.indexPath, "codebase.db");
12812
+ const invertedIndexPath = path20.join(this.indexPath, "inverted-index.json");
12813
+ const dbPath = path20.join(this.indexPath, "codebase.db");
11789
12814
  if (vectorsChanged || retryDue("vectors")) {
11790
- const vectorStoreExists = existsSync11(storePath);
11791
- const vectorMetadataExists = existsSync11(vectorMetadataPath);
12815
+ const vectorStoreExists = existsSync12(storePath);
12816
+ const vectorMetadataExists = existsSync12(vectorMetadataPath);
11792
12817
  if (vectorStoreExists && vectorMetadataExists) {
11793
12818
  try {
11794
12819
  const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
@@ -11803,8 +12828,8 @@ var Indexer = class _Indexer {
11803
12828
  setIssue("vectors", this.getVectorReadIssueMessage());
11804
12829
  }
11805
12830
  }
11806
- if (keywordChanged || retryDue("keyword") || !existsSync11(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
11807
- if (existsSync11(invertedIndexPath)) {
12831
+ if (keywordChanged || retryDue("keyword") || !existsSync12(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
12832
+ if (existsSync12(invertedIndexPath)) {
11808
12833
  try {
11809
12834
  const invertedIndex = new InvertedIndex(invertedIndexPath);
11810
12835
  invertedIndex.load();
@@ -11819,7 +12844,7 @@ var Indexer = class _Indexer {
11819
12844
  }
11820
12845
  }
11821
12846
  if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
11822
- if (existsSync11(dbPath)) {
12847
+ if (existsSync12(dbPath)) {
11823
12848
  try {
11824
12849
  const database = Database.openReadOnly(dbPath);
11825
12850
  if (this.database) {
@@ -11901,11 +12926,11 @@ var Indexer = class _Indexer {
11901
12926
  });
11902
12927
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
11903
12928
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
11904
- const storePath = path19.join(this.indexPath, "vectors");
12929
+ const storePath = path20.join(this.indexPath, "vectors");
11905
12930
  const vectorMetadataPath = `${storePath}.meta.json`;
11906
- const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11907
- const dbPath = path19.join(this.indexPath, "codebase.db");
11908
- let dbIsNew = !existsSync11(dbPath);
12931
+ const invertedIndexPath = path20.join(this.indexPath, "inverted-index.json");
12932
+ const dbPath = path20.join(this.indexPath, "codebase.db");
12933
+ let dbIsNew = !existsSync12(dbPath);
11909
12934
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
11910
12935
  if (mode === "writer") {
11911
12936
  await fsPromises3.mkdir(this.indexPath, { recursive: true });
@@ -11937,14 +12962,14 @@ var Indexer = class _Indexer {
11937
12962
  }
11938
12963
  }
11939
12964
  this.store = new VectorStore(storePath, dimensions);
11940
- if (existsSync11(storePath) || existsSync11(vectorMetadataPath)) {
12965
+ if (existsSync12(storePath) || existsSync12(vectorMetadataPath)) {
11941
12966
  this.store.load();
11942
12967
  }
11943
12968
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
11944
12969
  try {
11945
12970
  this.invertedIndex.load();
11946
12971
  } catch {
11947
- if (existsSync11(invertedIndexPath)) {
12972
+ if (existsSync12(invertedIndexPath)) {
11948
12973
  await fsPromises3.unlink(invertedIndexPath);
11949
12974
  }
11950
12975
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
@@ -11962,8 +12987,8 @@ var Indexer = class _Indexer {
11962
12987
  }
11963
12988
  } else {
11964
12989
  this.store = new VectorStore(storePath, dimensions);
11965
- const vectorStoreExists = existsSync11(storePath);
11966
- const vectorMetadataExists = existsSync11(vectorMetadataPath);
12990
+ const vectorStoreExists = existsSync12(storePath);
12991
+ const vectorMetadataExists = existsSync12(vectorMetadataPath);
11967
12992
  const vectorReadFailureMessage = this.getVectorReadIssueMessage();
11968
12993
  if (vectorStoreExists !== vectorMetadataExists) {
11969
12994
  this.recordReadIssue("vectors", vectorReadFailureMessage);
@@ -11976,7 +13001,7 @@ var Indexer = class _Indexer {
11976
13001
  }
11977
13002
  }
11978
13003
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
11979
- if (existsSync11(invertedIndexPath)) {
13004
+ if (existsSync12(invertedIndexPath)) {
11980
13005
  try {
11981
13006
  this.invertedIndex.load();
11982
13007
  } catch (error) {
@@ -11990,7 +13015,7 @@ var Indexer = class _Indexer {
11990
13015
  } else if (this.store.count() > 0) {
11991
13016
  this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
11992
13017
  }
11993
- if (existsSync11(dbPath)) {
13018
+ if (existsSync12(dbPath)) {
11994
13019
  try {
11995
13020
  this.database = Database.openReadOnly(dbPath);
11996
13021
  } catch (error) {
@@ -12082,7 +13107,7 @@ var Indexer = class _Indexer {
12082
13107
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
12083
13108
  return {
12084
13109
  resetCorruptedIndex: true,
12085
- warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
13110
+ warning: this.getCorruptedIndexWarning(path20.join(this.indexPath, "codebase.db"))
12086
13111
  };
12087
13112
  }
12088
13113
  throw error;
@@ -12097,7 +13122,7 @@ var Indexer = class _Indexer {
12097
13122
  return;
12098
13123
  }
12099
13124
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
12100
- const storeBasePath = path19.join(this.indexPath, "vectors");
13125
+ const storeBasePath = path20.join(this.indexPath, "vectors");
12101
13126
  const storeIndexPath = storeBasePath;
12102
13127
  const storeMetadataPath = `${storeBasePath}.meta.json`;
12103
13128
  const lease = this.requireActiveLease();
@@ -12107,19 +13132,19 @@ var Indexer = class _Indexer {
12107
13132
  let backedUpMetadata = false;
12108
13133
  let rebuiltCount = 0;
12109
13134
  let skippedCount = 0;
12110
- if (existsSync11(backupIndexPath)) {
13135
+ if (existsSync12(backupIndexPath)) {
12111
13136
  unlinkSync2(backupIndexPath);
12112
13137
  }
12113
- if (existsSync11(backupMetadataPath)) {
13138
+ if (existsSync12(backupMetadataPath)) {
12114
13139
  unlinkSync2(backupMetadataPath);
12115
13140
  }
12116
13141
  try {
12117
- if (existsSync11(storeIndexPath)) {
12118
- renameSync3(storeIndexPath, backupIndexPath);
13142
+ if (existsSync12(storeIndexPath)) {
13143
+ renameSync4(storeIndexPath, backupIndexPath);
12119
13144
  backedUpIndex = true;
12120
13145
  }
12121
- if (existsSync11(storeMetadataPath)) {
12122
- renameSync3(storeMetadataPath, backupMetadataPath);
13146
+ if (existsSync12(storeMetadataPath)) {
13147
+ renameSync4(storeMetadataPath, backupMetadataPath);
12123
13148
  backedUpMetadata = true;
12124
13149
  }
12125
13150
  store.clear();
@@ -12139,10 +13164,10 @@ var Indexer = class _Indexer {
12139
13164
  rebuiltCount += 1;
12140
13165
  }
12141
13166
  store.save();
12142
- if (backedUpIndex && existsSync11(backupIndexPath)) {
13167
+ if (backedUpIndex && existsSync12(backupIndexPath)) {
12143
13168
  unlinkSync2(backupIndexPath);
12144
13169
  }
12145
- if (backedUpMetadata && existsSync11(backupMetadataPath)) {
13170
+ if (backedUpMetadata && existsSync12(backupMetadataPath)) {
12146
13171
  unlinkSync2(backupMetadataPath);
12147
13172
  }
12148
13173
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -12155,17 +13180,17 @@ var Indexer = class _Indexer {
12155
13180
  store.clear();
12156
13181
  } catch {
12157
13182
  }
12158
- if (existsSync11(storeIndexPath)) {
13183
+ if (existsSync12(storeIndexPath)) {
12159
13184
  unlinkSync2(storeIndexPath);
12160
13185
  }
12161
- if (existsSync11(storeMetadataPath)) {
13186
+ if (existsSync12(storeMetadataPath)) {
12162
13187
  unlinkSync2(storeMetadataPath);
12163
13188
  }
12164
- if (backedUpIndex && existsSync11(backupIndexPath)) {
12165
- renameSync3(backupIndexPath, storeIndexPath);
13189
+ if (backedUpIndex && existsSync12(backupIndexPath)) {
13190
+ renameSync4(backupIndexPath, storeIndexPath);
12166
13191
  }
12167
- if (backedUpMetadata && existsSync11(backupMetadataPath)) {
12168
- renameSync3(backupMetadataPath, storeMetadataPath);
13192
+ if (backedUpMetadata && existsSync12(backupMetadataPath)) {
13193
+ renameSync4(backupMetadataPath, storeMetadataPath);
12169
13194
  }
12170
13195
  if (backedUpIndex || backedUpMetadata) {
12171
13196
  store.load();
@@ -12180,11 +13205,11 @@ var Indexer = class _Indexer {
12180
13205
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
12181
13206
  }
12182
13207
  async removeProjectRuntimeStateArtifacts() {
12183
- if (!existsSync11(this.indexPath)) return;
13208
+ if (!existsSync12(this.indexPath)) return;
12184
13209
  const names = await fsPromises3.readdir(this.indexPath);
12185
13210
  const runtimeStatePattern = /^(?:file-hashes|failed-batches)(?:\.[a-f0-9]{16})?\.json$/;
12186
13211
  await Promise.all(
12187
- names.filter((name) => runtimeStatePattern.test(name)).map((name) => fsPromises3.rm(path19.join(this.indexPath, name), { force: true }))
13212
+ names.filter((name) => runtimeStatePattern.test(name)).map((name) => fsPromises3.rm(path20.join(this.indexPath, name), { force: true }))
12188
13213
  );
12189
13214
  }
12190
13215
  async resetLocalIndexArtifacts() {
@@ -12200,13 +13225,13 @@ var Indexer = class _Indexer {
12200
13225
  this.readerArtifactRetryAfter.clear();
12201
13226
  this.fileHashCache.clear();
12202
13227
  const resetPaths = [
12203
- path19.join(this.indexPath, "codebase.db"),
12204
- path19.join(this.indexPath, "codebase.db-shm"),
12205
- path19.join(this.indexPath, "codebase.db-wal"),
12206
- path19.join(this.indexPath, "vectors"),
12207
- path19.join(this.indexPath, "vectors.usearch"),
12208
- path19.join(this.indexPath, "vectors.meta.json"),
12209
- path19.join(this.indexPath, "inverted-index.json")
13228
+ path20.join(this.indexPath, "codebase.db"),
13229
+ path20.join(this.indexPath, "codebase.db-shm"),
13230
+ path20.join(this.indexPath, "codebase.db-wal"),
13231
+ path20.join(this.indexPath, "vectors"),
13232
+ path20.join(this.indexPath, "vectors.usearch"),
13233
+ path20.join(this.indexPath, "vectors.meta.json"),
13234
+ path20.join(this.indexPath, "inverted-index.json")
12210
13235
  ];
12211
13236
  await Promise.all(resetPaths.map((targetPath) => fsPromises3.rm(targetPath, { recursive: true, force: true })));
12212
13237
  await this.removeProjectRuntimeStateArtifacts();
@@ -12216,7 +13241,7 @@ var Indexer = class _Indexer {
12216
13241
  if (!isSqliteCorruptionError(error)) {
12217
13242
  return false;
12218
13243
  }
12219
- const dbPath = path19.join(this.indexPath, "codebase.db");
13244
+ const dbPath = path20.join(this.indexPath, "codebase.db");
12220
13245
  const warning = this.getCorruptedIndexWarning(dbPath);
12221
13246
  const errorMessage = getErrorMessage4(error);
12222
13247
  if (this.config.scope === "global") {
@@ -12450,6 +13475,70 @@ var Indexer = class _Indexer {
12450
13475
  );
12451
13476
  return createCostEstimate(files, configuredProviderInfo);
12452
13477
  }
13478
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
13479
+ // estimateTokens over the embedding text of every indexable chunk, without
13480
+ // calling the embedding provider or writing to the index. Read-only and
13481
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
13482
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
13483
+ // an upper bound because cached chunks are counted here but not re-embedded.
13484
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
13485
+ // denominator that matches the live "Tokens used" basis.
13486
+ async dryRunCost() {
13487
+ const { configuredProviderInfo } = await this.ensureInitialized();
13488
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
13489
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
13490
+ const { files } = await collectFiles(
13491
+ this.materializedProjectRoot,
13492
+ includePatterns,
13493
+ this.config.exclude,
13494
+ this.config.indexing.maxFileSize,
13495
+ this.getMaterializedKnowledgeBases(),
13496
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
13497
+ );
13498
+ let filesCount = 0;
13499
+ let chunksCount = 0;
13500
+ let tokensToEmbed = 0;
13501
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
13502
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
13503
+ try {
13504
+ return {
13505
+ path: this.toStoredFilePath(f.path),
13506
+ content: await fsPromises3.readFile(f.path, "utf-8")
13507
+ };
13508
+ } catch {
13509
+ return null;
13510
+ }
13511
+ }));
13512
+ const readable = loadedFiles.filter(
13513
+ (f) => f !== null
13514
+ );
13515
+ filesCount += readable.length;
13516
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
13517
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
13518
+ for (const parsed of parsedFiles) {
13519
+ let chunksToProcess = parsed.chunks;
13520
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
13521
+ const content = contentByPath.get(parsed.path);
13522
+ if (content !== void 0) {
13523
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
13524
+ }
13525
+ }
13526
+ chunksToProcess = selectIndexableChunks(
13527
+ chunksToProcess,
13528
+ this.config.indexing.maxChunksPerFile,
13529
+ this.config.indexing.semanticOnly
13530
+ );
13531
+ for (const chunk of chunksToProcess) {
13532
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
13533
+ chunksCount += 1;
13534
+ for (const text of texts) {
13535
+ tokensToEmbed += estimateTokens(text);
13536
+ }
13537
+ }
13538
+ }
13539
+ }
13540
+ return { filesCount, chunksCount, tokensToEmbed };
13541
+ }
12453
13542
  async index(onProgress) {
12454
13543
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
12455
13544
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -12539,10 +13628,10 @@ var Indexer = class _Indexer {
12539
13628
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
12540
13629
  const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
12541
13630
  const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
12542
- if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".swift")) {
13631
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path20.extname(filePath).toLowerCase() === ".swift")) {
12543
13632
  this.logger.info("Reindexing cached Swift files for parser support");
12544
13633
  }
12545
- if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".metal")) {
13634
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path20.extname(filePath).toLowerCase() === ".metal")) {
12546
13635
  this.logger.info("Reindexing cached Metal files for parser support");
12547
13636
  }
12548
13637
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
@@ -12586,8 +13675,8 @@ var Indexer = class _Indexer {
12586
13675
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
12587
13676
  (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
12588
13677
  );
12589
- const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12590
- const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
13678
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path20.extname(storedPath).toLowerCase() === ".swift";
13679
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path20.extname(storedPath).toLowerCase() === ".metal";
12591
13680
  const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
12592
13681
  if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12593
13682
  unchangedFilePaths.add(storedPath);
@@ -12646,7 +13735,7 @@ var Indexer = class _Indexer {
12646
13735
  }
12647
13736
  }
12648
13737
  }
12649
- const shouldRetryFailedPath = (filePath) => filePath !== null && currentFileHashes.has(filePath) && unchangedFilePaths.has(filePath);
13738
+ const shouldRetryFailedPath = (filePath) => filePath !== null && !this.isStoredPathExcluded(filePath) && currentFileHashes.has(filePath) && unchangedFilePaths.has(filePath);
12650
13739
  const failedProcessing = this.prepareFailedBatchProcessing(scopedRoots, shouldRetryFailedPath);
12651
13740
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12652
13741
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
@@ -12750,7 +13839,7 @@ var Indexer = class _Indexer {
12750
13839
  throw new Error(`Parsed file was not present in its source batch: ${parsed.path}`);
12751
13840
  }
12752
13841
  if (parsed.chunks.length === 0) {
12753
- stats.parseFailures.push(path19.isAbsolute(parsed.path) ? path19.relative(this.projectRoot, parsed.path) : parsed.path);
13842
+ stats.parseFailures.push(path20.isAbsolute(parsed.path) ? path20.relative(this.projectRoot, parsed.path) : parsed.path);
12754
13843
  }
12755
13844
  let chunksToProcess = parsed.chunks;
12756
13845
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
@@ -13085,8 +14174,8 @@ var Indexer = class _Indexer {
13085
14174
  previousBranchSymbolIds,
13086
14175
  Array.from(allSymbolIds)
13087
14176
  );
13088
- const vectorPath = path19.join(this.indexPath, "vectors");
13089
- const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync11(vectorPath) && existsSync11(`${vectorPath}.meta.json`);
14177
+ const vectorPath = path20.join(this.indexPath, "vectors");
14178
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync12(vectorPath) && existsSync12(`${vectorPath}.meta.json`);
13090
14179
  if (backfilledBlameMetadata || shouldFingerprintLegacyPair || removedStoredChunks) {
13091
14180
  store.save();
13092
14181
  }
@@ -13881,7 +14970,7 @@ var Indexer = class _Indexer {
13881
14970
  const missingChunkKeys = [];
13882
14971
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
13883
14972
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
13884
- if (!existsSync11(this.toMaterializedFilePath(filePath))) {
14973
+ if (!existsSync12(this.toMaterializedFilePath(filePath))) {
13885
14974
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
13886
14975
  for (const key of chunkKeys) {
13887
14976
  missingChunkKeys.push(key);
@@ -13944,7 +15033,7 @@ var Indexer = class _Indexer {
13944
15033
  gcOrphanSymbols: 0,
13945
15034
  gcOrphanCallEdges: 0,
13946
15035
  resetCorruptedIndex: true,
13947
- warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
15036
+ warning: this.getCorruptedIndexWarning(path20.join(this.indexPath, "codebase.db"))
13948
15037
  };
13949
15038
  }
13950
15039
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -13974,7 +15063,8 @@ var Indexer = class _Indexer {
13974
15063
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
13975
15064
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
13976
15065
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
13977
- const failedProcessing = this.prepareFailedBatchProcessing(roots, () => true);
15066
+ const shouldProcessFailedPath = (filePath) => filePath === null || !this.isStoredPathExcluded(filePath);
15067
+ const failedProcessing = this.prepareFailedBatchProcessing(roots, shouldProcessFailedPath);
13978
15068
  if (failedProcessing.latestById.size === 0) {
13979
15069
  this.finalizeFailedBatchWriteState(failedProcessing.state);
13980
15070
  return { succeeded: 0, failed: 0, remaining: 0 };
@@ -13987,7 +15077,7 @@ var Indexer = class _Indexer {
13987
15077
  const retryableChunks = this.iterateLatestFailedChunks(
13988
15078
  failedProcessing.latestById,
13989
15079
  roots,
13990
- () => true,
15080
+ shouldProcessFailedPath,
13991
15081
  maxChunkTokens
13992
15082
  );
13993
15083
  for (const retryBatch of iterateOrderedFileBatches(
@@ -14257,9 +15347,9 @@ var Indexer = class _Indexer {
14257
15347
  this.requireReadableComponents(readIssues, "database");
14258
15348
  let shortest = [];
14259
15349
  for (const branchKey of this.getBranchCatalogKeys()) {
14260
- const path30 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
14261
- if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14262
- shortest = path30;
15350
+ const path31 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
15351
+ if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
15352
+ shortest = path31;
14263
15353
  }
14264
15354
  }
14265
15355
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14307,13 +15397,13 @@ var Indexer = class _Indexer {
14307
15397
  }
14308
15398
  }
14309
15399
  if (!found) continue;
14310
- const path30 = [];
15400
+ const path31 = [];
14311
15401
  let currentSymbolId = toSymbolId;
14312
15402
  while (true) {
14313
15403
  const symbol = symbolsById.get(currentSymbolId);
14314
15404
  if (!symbol) break;
14315
15405
  const parent = parentBySymbolId.get(currentSymbolId);
14316
- path30.push({
15406
+ path31.push({
14317
15407
  symbolId: symbol.id,
14318
15408
  symbolName: symbol.name,
14319
15409
  filePath: symbol.filePath,
@@ -14323,9 +15413,9 @@ var Indexer = class _Indexer {
14323
15413
  if (!parent) break;
14324
15414
  currentSymbolId = parent.parentId;
14325
15415
  }
14326
- path30.reverse();
14327
- if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14328
- shortest = path30;
15416
+ path31.reverse();
15417
+ if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
15418
+ shortest = path31;
14329
15419
  }
14330
15420
  }
14331
15421
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14476,7 +15566,7 @@ var Indexer = class _Indexer {
14476
15566
  );
14477
15567
  }
14478
15568
  }
14479
- const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path19.resolve(this.projectRoot, filePath)));
15569
+ const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path20.resolve(this.projectRoot, filePath)));
14480
15570
  const storedChangedFiles = toStoredChangedFiles(changedFiles);
14481
15571
  const directSymbols = database.getSymbolsForFiles(storedChangedFiles, branchKey);
14482
15572
  const directIds = directSymbols.map((s) => s.id);
@@ -14625,12 +15715,12 @@ var Indexer = class _Indexer {
14625
15715
  if (meta.filePath) filePaths.add(meta.filePath);
14626
15716
  }
14627
15717
  const directory = options?.directory?.replace(/\/$/, "");
14628
- const absoluteDirectoryFilter = directory ? path19.resolve(this.projectRoot, directory) : void 0;
15718
+ const absoluteDirectoryFilter = directory ? path20.resolve(this.projectRoot, directory) : void 0;
14629
15719
  for (const filePath of filePaths) {
14630
15720
  if (directory) {
14631
15721
  const absoluteFilePath = this.resolveStoredFilePath(filePath);
14632
15722
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
14633
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path19.sep));
15723
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path20.sep));
14634
15724
  if (!matchesRelative && !matchesProjectRelative) {
14635
15725
  continue;
14636
15726
  }
@@ -14747,15 +15837,24 @@ function getOrCreateIndexer(projectRoot, host) {
14747
15837
  }
14748
15838
  const indexer = new Indexer(projectRoot, config, host);
14749
15839
  indexerCache.set(key, indexer);
14750
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15840
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15841
+ preserveManagedWorker: true,
15842
+ synchronizeBackgroundWorker: false
15843
+ });
14751
15844
  return indexer;
14752
15845
  }
14753
- function initializeTools(projectRoot, config, host) {
15846
+ function initializeTools(projectRoot, config, host, options = {}) {
14754
15847
  defaultProjectRoots.set(host, projectRoot);
14755
15848
  const key = getIndexerCacheKey(projectRoot, host);
15849
+ if (options.preserveManagedWorker === true && isBackgroundWorkerManaged(projectRoot, host) && indexerCache.has(key)) {
15850
+ return;
15851
+ }
14756
15852
  configCache.set(key, config);
14757
15853
  indexerCache.set(key, new Indexer(projectRoot, config, host));
14758
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15854
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15855
+ preserveManagedWorker: options.preserveManagedWorker,
15856
+ synchronizeBackgroundWorker: false
15857
+ });
14759
15858
  }
14760
15859
  function getIndexerForProject(projectRoot, host) {
14761
15860
  const root = getProjectRoot(projectRoot, host);
@@ -14769,7 +15868,9 @@ function refreshIndexerForDirectory(projectRoot, host, config = parseConfig(load
14769
15868
  const key = getIndexerCacheKey(projectRoot, host);
14770
15869
  configCache.set(key, config);
14771
15870
  indexerCache.set(key, new Indexer(projectRoot, config, host));
14772
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15871
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15872
+ synchronizeBackgroundWorker: true
15873
+ });
14773
15874
  return config;
14774
15875
  }
14775
15876
  var AutoIndexRetrievalUnavailableError = class extends Error {
@@ -14796,7 +15897,7 @@ function trimOrUndefined(value) {
14796
15897
  return normalized || void 0;
14797
15898
  }
14798
15899
  function normalizeCallGraphPath(value) {
14799
- let normalized = path20.posix.normalize(value.trim().replaceAll("\\", "/"));
15900
+ let normalized = path21.posix.normalize(value.trim().replaceAll("\\", "/"));
14800
15901
  if (normalized.startsWith("./")) {
14801
15902
  normalized = normalized.slice(2);
14802
15903
  }
@@ -14989,12 +16090,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14989
16090
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14990
16091
  return { from: fromResolution, to: toResolution, path: [] };
14991
16092
  }
14992
- const path30 = await indexer.findCallPathBySymbolIds(
16093
+ const path31 = await indexer.findCallPathBySymbolIds(
14993
16094
  fromResolution.symbolId,
14994
16095
  toResolution.symbolId,
14995
16096
  maxDepth
14996
16097
  );
14997
- return { from: fromResolution, to: toResolution, path: path30 };
16098
+ return { from: fromResolution, to: toResolution, path: path31 };
14998
16099
  }
14999
16100
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
15000
16101
  const root = getProjectRoot(projectRoot, host);
@@ -15003,6 +16104,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15003
16104
  if (args.estimateOnly) {
15004
16105
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15005
16106
  }
16107
+ if (args.dryRun) {
16108
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
16109
+ }
15006
16110
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15007
16111
  if (onProgress) {
15008
16112
  void onProgress(formatProgressTitle(progress), {
@@ -15177,15 +16281,15 @@ async function getIndexLogs(projectRoot, host, args) {
15177
16281
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15178
16282
  const root = getProjectRoot(projectRoot, host);
15179
16283
  const inputPath = knowledgeBasePath.trim();
15180
- const normalizedPath2 = path20.resolve(
15181
- path20.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
16284
+ const normalizedPath2 = path21.resolve(
16285
+ path21.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
15182
16286
  );
15183
- if (!existsSync12(normalizedPath2)) {
16287
+ if (!existsSync13(normalizedPath2)) {
15184
16288
  return `Error: Directory does not exist: ${normalizedPath2}`;
15185
16289
  }
15186
16290
  let realPath;
15187
16291
  try {
15188
- realPath = realpathSync5(normalizedPath2);
16292
+ realPath = realpathSync6(normalizedPath2);
15189
16293
  } catch {
15190
16294
  return `Error: Cannot resolve path: ${normalizedPath2}`;
15191
16295
  }
@@ -15214,7 +16318,7 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15214
16318
  }
15215
16319
  }
15216
16320
  for (const dotDir of sensitiveDotDirs) {
15217
- const sensitiveDir = path20.join(homeDir, dotDir);
16321
+ const sensitiveDir = path21.join(homeDir, dotDir);
15218
16322
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
15219
16323
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
15220
16324
  }
@@ -15260,7 +16364,7 @@ function listKnowledgeBases(projectRoot, host) {
15260
16364
  for (let i = 0; i < knowledgeBases.length; i++) {
15261
16365
  const kb = knowledgeBases[i];
15262
16366
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
15263
- const exists = existsSync12(resolvedPath);
16367
+ const exists = existsSync13(resolvedPath);
15264
16368
  result += `[${i + 1}] ${kb}
15265
16369
  `;
15266
16370
  result += ` Resolved: ${resolvedPath}
@@ -15277,7 +16381,7 @@ function listKnowledgeBases(projectRoot, host) {
15277
16381
  }
15278
16382
  result += "\n";
15279
16383
  }
15280
- const hasHostConfig = existsSync12(path20.join(root, getHostProjectConfigRelativePath(host)));
16384
+ const hasHostConfig = existsSync13(path21.join(root, getHostProjectConfigRelativePath(host)));
15281
16385
  if (hasHostConfig) {
15282
16386
  result += `
15283
16387
  Config sources: 1 file(s).`;
@@ -15311,7 +16415,7 @@ Run /index to rebuild the index without the removed knowledge base.`;
15311
16415
  }
15312
16416
 
15313
16417
  // src/watcher/file-watcher.ts
15314
- import { existsSync as existsSync13, statSync as statSync6 } from "fs";
16418
+ import { existsSync as existsSync14, statSync as statSync6 } from "fs";
15315
16419
 
15316
16420
  // node_modules/chokidar/index.js
15317
16421
  import { EventEmitter as EventEmitter2 } from "events";
@@ -15403,7 +16507,7 @@ var ReaddirpStream = class extends Readable {
15403
16507
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
15404
16508
  const statMethod = opts.lstat ? lstat : stat;
15405
16509
  if (wantBigintFsStats) {
15406
- this._stat = (path30) => statMethod(path30, { bigint: true });
16510
+ this._stat = (path31) => statMethod(path31, { bigint: true });
15407
16511
  } else {
15408
16512
  this._stat = statMethod;
15409
16513
  }
@@ -15428,8 +16532,8 @@ var ReaddirpStream = class extends Readable {
15428
16532
  const par = this.parent;
15429
16533
  const fil = par && par.files;
15430
16534
  if (fil && fil.length > 0) {
15431
- const { path: path30, depth } = par;
15432
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path30));
16535
+ const { path: path31, depth } = par;
16536
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
15433
16537
  const awaited = await Promise.all(slice);
15434
16538
  for (const entry of awaited) {
15435
16539
  if (!entry)
@@ -15469,21 +16573,21 @@ var ReaddirpStream = class extends Readable {
15469
16573
  this.reading = false;
15470
16574
  }
15471
16575
  }
15472
- async _exploreDir(path30, depth) {
16576
+ async _exploreDir(path31, depth) {
15473
16577
  let files;
15474
16578
  try {
15475
- files = await readdir(path30, this._rdOptions);
16579
+ files = await readdir(path31, this._rdOptions);
15476
16580
  } catch (error) {
15477
16581
  this._onError(error);
15478
16582
  }
15479
- return { files, depth, path: path30 };
16583
+ return { files, depth, path: path31 };
15480
16584
  }
15481
- async _formatEntry(dirent, path30) {
16585
+ async _formatEntry(dirent, path31) {
15482
16586
  let entry;
15483
- const basename9 = this._isDirent ? dirent.name : dirent;
16587
+ const basename10 = this._isDirent ? dirent.name : dirent;
15484
16588
  try {
15485
- const fullPath = presolve(pjoin(path30, basename9));
15486
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
16589
+ const fullPath = presolve(pjoin(path31, basename10));
16590
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename10 };
15487
16591
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
15488
16592
  } catch (err) {
15489
16593
  this._onError(err);
@@ -15882,16 +16986,16 @@ var delFromSet = (main, prop, item) => {
15882
16986
  };
15883
16987
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
15884
16988
  var FsWatchInstances = /* @__PURE__ */ new Map();
15885
- function createFsWatchInstance(path30, options, listener, errHandler, emitRaw) {
16989
+ function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
15886
16990
  const handleEvent = (rawEvent, 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));
16991
+ listener(path31);
16992
+ emitRaw(rawEvent, evPath, { watchedPath: path31 });
16993
+ if (evPath && path31 !== evPath) {
16994
+ fsWatchBroadcast(sp.resolve(path31, evPath), KEY_LISTENERS, sp.join(path31, evPath));
15891
16995
  }
15892
16996
  };
15893
16997
  try {
15894
- return fs_watch(path30, {
16998
+ return fs_watch(path31, {
15895
16999
  persistent: options.persistent
15896
17000
  }, handleEvent);
15897
17001
  } catch (error) {
@@ -15907,12 +17011,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
15907
17011
  listener(val1, val2, val3);
15908
17012
  });
15909
17013
  };
15910
- var setFsWatchListener = (path30, fullPath, options, handlers) => {
17014
+ var setFsWatchListener = (path31, fullPath, options, handlers) => {
15911
17015
  const { listener, errHandler, rawEmitter } = handlers;
15912
17016
  let cont = FsWatchInstances.get(fullPath);
15913
17017
  let watcher;
15914
17018
  if (!options.persistent) {
15915
- watcher = createFsWatchInstance(path30, options, listener, errHandler, rawEmitter);
17019
+ watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
15916
17020
  if (!watcher)
15917
17021
  return;
15918
17022
  return watcher.close.bind(watcher);
@@ -15923,7 +17027,7 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15923
17027
  addAndConvert(cont, KEY_RAW, rawEmitter);
15924
17028
  } else {
15925
17029
  watcher = createFsWatchInstance(
15926
- path30,
17030
+ path31,
15927
17031
  options,
15928
17032
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
15929
17033
  errHandler,
@@ -15938,7 +17042,7 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15938
17042
  cont.watcherUnusable = true;
15939
17043
  if (isWindows && error.code === "EPERM") {
15940
17044
  try {
15941
- const fd = await open(path30, "r");
17045
+ const fd = await open(path31, "r");
15942
17046
  await fd.close();
15943
17047
  broadcastErr(error);
15944
17048
  } catch (err) {
@@ -15969,7 +17073,7 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15969
17073
  };
15970
17074
  };
15971
17075
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
15972
- var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
17076
+ var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
15973
17077
  const { listener, rawEmitter } = handlers;
15974
17078
  let cont = FsWatchFileInstances.get(fullPath);
15975
17079
  const copts = cont && cont.options;
@@ -15991,7 +17095,7 @@ var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
15991
17095
  });
15992
17096
  const currmtime = curr.mtimeMs;
15993
17097
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
15994
- foreach(cont.listeners, (listener2) => listener2(path30, curr));
17098
+ foreach(cont.listeners, (listener2) => listener2(path31, curr));
15995
17099
  }
15996
17100
  })
15997
17101
  };
@@ -16021,13 +17125,13 @@ var NodeFsHandler = class {
16021
17125
  * @param listener on fs change
16022
17126
  * @returns closer for the watcher instance
16023
17127
  */
16024
- _watchWithNodeFs(path30, listener) {
17128
+ _watchWithNodeFs(path31, listener) {
16025
17129
  const opts = this.fsw.options;
16026
- const directory = sp.dirname(path30);
16027
- const basename9 = sp.basename(path30);
17130
+ const directory = sp.dirname(path31);
17131
+ const basename10 = sp.basename(path31);
16028
17132
  const parent = this.fsw._getWatchedDir(directory);
16029
- parent.add(basename9);
16030
- const absolutePath = sp.resolve(path30);
17133
+ parent.add(basename10);
17134
+ const absolutePath = sp.resolve(path31);
16031
17135
  const options = {
16032
17136
  persistent: opts.persistent
16033
17137
  };
@@ -16036,13 +17140,13 @@ var NodeFsHandler = class {
16036
17140
  let closer;
16037
17141
  if (opts.usePolling) {
16038
17142
  const enableBin = opts.interval !== opts.binaryInterval;
16039
- options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
16040
- closer = setFsWatchFileListener(path30, absolutePath, options, {
17143
+ options.interval = enableBin && isBinaryPath(basename10) ? opts.binaryInterval : opts.interval;
17144
+ closer = setFsWatchFileListener(path31, absolutePath, options, {
16041
17145
  listener,
16042
17146
  rawEmitter: this.fsw._emitRaw
16043
17147
  });
16044
17148
  } else {
16045
- closer = setFsWatchListener(path30, absolutePath, options, {
17149
+ closer = setFsWatchListener(path31, absolutePath, options, {
16046
17150
  listener,
16047
17151
  errHandler: this._boundHandleError,
16048
17152
  rawEmitter: this.fsw._emitRaw
@@ -16058,13 +17162,13 @@ var NodeFsHandler = class {
16058
17162
  if (this.fsw.closed) {
16059
17163
  return;
16060
17164
  }
16061
- const dirname15 = sp.dirname(file);
16062
- const basename9 = sp.basename(file);
16063
- const parent = this.fsw._getWatchedDir(dirname15);
17165
+ const dirname16 = sp.dirname(file);
17166
+ const basename10 = sp.basename(file);
17167
+ const parent = this.fsw._getWatchedDir(dirname16);
16064
17168
  let prevStats = stats;
16065
- if (parent.has(basename9))
17169
+ if (parent.has(basename10))
16066
17170
  return;
16067
- const listener = async (path30, newStats) => {
17171
+ const listener = async (path31, newStats) => {
16068
17172
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
16069
17173
  return;
16070
17174
  if (!newStats || newStats.mtimeMs === 0) {
@@ -16078,18 +17182,18 @@ var NodeFsHandler = class {
16078
17182
  this.fsw._emit(EV.CHANGE, file, newStats2);
16079
17183
  }
16080
17184
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
16081
- this.fsw._closeFile(path30);
17185
+ this.fsw._closeFile(path31);
16082
17186
  prevStats = newStats2;
16083
17187
  const closer2 = this._watchWithNodeFs(file, listener);
16084
17188
  if (closer2)
16085
- this.fsw._addPathCloser(path30, closer2);
17189
+ this.fsw._addPathCloser(path31, closer2);
16086
17190
  } else {
16087
17191
  prevStats = newStats2;
16088
17192
  }
16089
17193
  } catch (error) {
16090
- this.fsw._remove(dirname15, basename9);
17194
+ this.fsw._remove(dirname16, basename10);
16091
17195
  }
16092
- } else if (parent.has(basename9)) {
17196
+ } else if (parent.has(basename10)) {
16093
17197
  const at = newStats.atimeMs;
16094
17198
  const mt = newStats.mtimeMs;
16095
17199
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -16114,7 +17218,7 @@ var NodeFsHandler = class {
16114
17218
  * @param item basename of this item
16115
17219
  * @returns true if no more processing is needed for this entry.
16116
17220
  */
16117
- async _handleSymlink(entry, directory, path30, item) {
17221
+ async _handleSymlink(entry, directory, path31, item) {
16118
17222
  if (this.fsw.closed) {
16119
17223
  return;
16120
17224
  }
@@ -16124,7 +17228,7 @@ var NodeFsHandler = class {
16124
17228
  this.fsw._incrReadyCount();
16125
17229
  let linkPath;
16126
17230
  try {
16127
- linkPath = await fsrealpath(path30);
17231
+ linkPath = await fsrealpath(path31);
16128
17232
  } catch (e) {
16129
17233
  this.fsw._emitReady();
16130
17234
  return true;
@@ -16134,12 +17238,12 @@ var NodeFsHandler = class {
16134
17238
  if (dir.has(item)) {
16135
17239
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
16136
17240
  this.fsw._symlinkPaths.set(full, linkPath);
16137
- this.fsw._emit(EV.CHANGE, path30, entry.stats);
17241
+ this.fsw._emit(EV.CHANGE, path31, entry.stats);
16138
17242
  }
16139
17243
  } else {
16140
17244
  dir.add(item);
16141
17245
  this.fsw._symlinkPaths.set(full, linkPath);
16142
- this.fsw._emit(EV.ADD, path30, entry.stats);
17246
+ this.fsw._emit(EV.ADD, path31, entry.stats);
16143
17247
  }
16144
17248
  this.fsw._emitReady();
16145
17249
  return true;
@@ -16169,9 +17273,9 @@ var NodeFsHandler = class {
16169
17273
  return;
16170
17274
  }
16171
17275
  const item = entry.path;
16172
- let path30 = sp.join(directory, item);
17276
+ let path31 = sp.join(directory, item);
16173
17277
  current.add(item);
16174
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path30, item)) {
17278
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
16175
17279
  return;
16176
17280
  }
16177
17281
  if (this.fsw.closed) {
@@ -16180,11 +17284,11 @@ var NodeFsHandler = class {
16180
17284
  }
16181
17285
  if (item === target || !target && !previous.has(item)) {
16182
17286
  this.fsw._incrReadyCount();
16183
- path30 = sp.join(dir, sp.relative(dir, path30));
16184
- this._addToNodeFs(path30, initialAdd, wh, depth + 1);
17287
+ path31 = sp.join(dir, sp.relative(dir, path31));
17288
+ this._addToNodeFs(path31, initialAdd, wh, depth + 1);
16185
17289
  }
16186
17290
  }).on(EV.ERROR, this._boundHandleError);
16187
- return new Promise((resolve17, reject) => {
17291
+ return new Promise((resolve18, reject) => {
16188
17292
  if (!stream)
16189
17293
  return reject();
16190
17294
  stream.once(STR_END, () => {
@@ -16193,7 +17297,7 @@ var NodeFsHandler = class {
16193
17297
  return;
16194
17298
  }
16195
17299
  const wasThrottled = throttler ? throttler.clear() : false;
16196
- resolve17(void 0);
17300
+ resolve18(void 0);
16197
17301
  previous.getChildren().filter((item) => {
16198
17302
  return item !== directory && !current.has(item);
16199
17303
  }).forEach((item) => {
@@ -16250,13 +17354,13 @@ var NodeFsHandler = class {
16250
17354
  * @param depth Child path actually targeted for watch
16251
17355
  * @param target Child path actually targeted for watch
16252
17356
  */
16253
- async _addToNodeFs(path30, initialAdd, priorWh, depth, target) {
17357
+ async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
16254
17358
  const ready = this.fsw._emitReady;
16255
- if (this.fsw._isIgnored(path30) || this.fsw.closed) {
17359
+ if (this.fsw._isIgnored(path31) || this.fsw.closed) {
16256
17360
  ready();
16257
17361
  return false;
16258
17362
  }
16259
- const wh = this.fsw._getWatchHelpers(path30);
17363
+ const wh = this.fsw._getWatchHelpers(path31);
16260
17364
  if (priorWh) {
16261
17365
  wh.filterPath = (entry) => priorWh.filterPath(entry);
16262
17366
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -16272,8 +17376,8 @@ var NodeFsHandler = class {
16272
17376
  const follow = this.fsw.options.followSymlinks;
16273
17377
  let closer;
16274
17378
  if (stats.isDirectory()) {
16275
- const absPath = sp.resolve(path30);
16276
- const targetPath = follow ? await fsrealpath(path30) : path30;
17379
+ const absPath = sp.resolve(path31);
17380
+ const targetPath = follow ? await fsrealpath(path31) : path31;
16277
17381
  if (this.fsw.closed)
16278
17382
  return;
16279
17383
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -16283,29 +17387,29 @@ var NodeFsHandler = class {
16283
17387
  this.fsw._symlinkPaths.set(absPath, targetPath);
16284
17388
  }
16285
17389
  } else if (stats.isSymbolicLink()) {
16286
- const targetPath = follow ? await fsrealpath(path30) : path30;
17390
+ const targetPath = follow ? await fsrealpath(path31) : path31;
16287
17391
  if (this.fsw.closed)
16288
17392
  return;
16289
17393
  const parent = sp.dirname(wh.watchPath);
16290
17394
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
16291
17395
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
16292
- closer = await this._handleDir(parent, stats, initialAdd, depth, path30, wh, targetPath);
17396
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
16293
17397
  if (this.fsw.closed)
16294
17398
  return;
16295
17399
  if (targetPath !== void 0) {
16296
- this.fsw._symlinkPaths.set(sp.resolve(path30), targetPath);
17400
+ this.fsw._symlinkPaths.set(sp.resolve(path31), targetPath);
16297
17401
  }
16298
17402
  } else {
16299
17403
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
16300
17404
  }
16301
17405
  ready();
16302
17406
  if (closer)
16303
- this.fsw._addPathCloser(path30, closer);
17407
+ this.fsw._addPathCloser(path31, closer);
16304
17408
  return false;
16305
17409
  } catch (error) {
16306
17410
  if (this.fsw._handleError(error)) {
16307
17411
  ready();
16308
- return path30;
17412
+ return path31;
16309
17413
  }
16310
17414
  }
16311
17415
  }
@@ -16348,24 +17452,24 @@ function createPattern(matcher) {
16348
17452
  }
16349
17453
  return () => false;
16350
17454
  }
16351
- function normalizePath2(path30) {
16352
- if (typeof path30 !== "string")
17455
+ function normalizePath2(path31) {
17456
+ if (typeof path31 !== "string")
16353
17457
  throw new Error("string expected");
16354
- path30 = sp2.normalize(path30);
16355
- path30 = path30.replace(/\\/g, "/");
17458
+ path31 = sp2.normalize(path31);
17459
+ path31 = path31.replace(/\\/g, "/");
16356
17460
  let prepend = false;
16357
- if (path30.startsWith("//"))
17461
+ if (path31.startsWith("//"))
16358
17462
  prepend = true;
16359
- path30 = path30.replace(DOUBLE_SLASH_RE, "/");
17463
+ path31 = path31.replace(DOUBLE_SLASH_RE, "/");
16360
17464
  if (prepend)
16361
- path30 = "/" + path30;
16362
- return path30;
17465
+ path31 = "/" + path31;
17466
+ return path31;
16363
17467
  }
16364
17468
  function matchPatterns(patterns, testString, stats) {
16365
- const path30 = normalizePath2(testString);
17469
+ const path31 = normalizePath2(testString);
16366
17470
  for (let index = 0; index < patterns.length; index++) {
16367
17471
  const pattern = patterns[index];
16368
- if (pattern(path30, stats)) {
17472
+ if (pattern(path31, stats)) {
16369
17473
  return true;
16370
17474
  }
16371
17475
  }
@@ -16403,19 +17507,19 @@ var toUnix = (string) => {
16403
17507
  }
16404
17508
  return str;
16405
17509
  };
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));
17510
+ var normalizePathToUnix = (path31) => toUnix(sp2.normalize(toUnix(path31)));
17511
+ var normalizeIgnored = (cwd = "") => (path31) => {
17512
+ if (typeof path31 === "string") {
17513
+ return normalizePathToUnix(sp2.isAbsolute(path31) ? path31 : sp2.join(cwd, path31));
16410
17514
  } else {
16411
- return path30;
17515
+ return path31;
16412
17516
  }
16413
17517
  };
16414
- var getAbsolutePath = (path30, cwd) => {
16415
- if (sp2.isAbsolute(path30)) {
16416
- return path30;
17518
+ var getAbsolutePath = (path31, cwd) => {
17519
+ if (sp2.isAbsolute(path31)) {
17520
+ return path31;
16417
17521
  }
16418
- return sp2.join(cwd, path30);
17522
+ return sp2.join(cwd, path31);
16419
17523
  };
16420
17524
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
16421
17525
  var DirEntry = class {
@@ -16480,10 +17584,10 @@ var WatchHelper = class {
16480
17584
  dirParts;
16481
17585
  followSymlinks;
16482
17586
  statMethod;
16483
- constructor(path30, follow, fsw) {
17587
+ constructor(path31, follow, fsw) {
16484
17588
  this.fsw = fsw;
16485
- const watchPath = path30;
16486
- this.path = path30 = path30.replace(REPLACER_RE, "");
17589
+ const watchPath = path31;
17590
+ this.path = path31 = path31.replace(REPLACER_RE, "");
16487
17591
  this.watchPath = watchPath;
16488
17592
  this.fullWatchPath = sp2.resolve(watchPath);
16489
17593
  this.dirParts = [];
@@ -16623,20 +17727,20 @@ var FSWatcher = class extends EventEmitter2 {
16623
17727
  this._closePromise = void 0;
16624
17728
  let paths = unifyPaths(paths_);
16625
17729
  if (cwd) {
16626
- paths = paths.map((path30) => {
16627
- const absPath = getAbsolutePath(path30, cwd);
17730
+ paths = paths.map((path31) => {
17731
+ const absPath = getAbsolutePath(path31, cwd);
16628
17732
  return absPath;
16629
17733
  });
16630
17734
  }
16631
- paths.forEach((path30) => {
16632
- this._removeIgnoredPath(path30);
17735
+ paths.forEach((path31) => {
17736
+ this._removeIgnoredPath(path31);
16633
17737
  });
16634
17738
  this._userIgnored = void 0;
16635
17739
  if (!this._readyCount)
16636
17740
  this._readyCount = 0;
16637
17741
  this._readyCount += paths.length;
16638
- Promise.all(paths.map(async (path30) => {
16639
- const res = await this._nodeFsHandler._addToNodeFs(path30, !_internal, void 0, 0, _origAdd);
17742
+ Promise.all(paths.map(async (path31) => {
17743
+ const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
16640
17744
  if (res)
16641
17745
  this._emitReady();
16642
17746
  return res;
@@ -16658,17 +17762,17 @@ var FSWatcher = class extends EventEmitter2 {
16658
17762
  return this;
16659
17763
  const paths = unifyPaths(paths_);
16660
17764
  const { cwd } = this.options;
16661
- paths.forEach((path30) => {
16662
- if (!sp2.isAbsolute(path30) && !this._closers.has(path30)) {
17765
+ paths.forEach((path31) => {
17766
+ if (!sp2.isAbsolute(path31) && !this._closers.has(path31)) {
16663
17767
  if (cwd)
16664
- path30 = sp2.join(cwd, path30);
16665
- path30 = sp2.resolve(path30);
17768
+ path31 = sp2.join(cwd, path31);
17769
+ path31 = sp2.resolve(path31);
16666
17770
  }
16667
- this._closePath(path30);
16668
- this._addIgnoredPath(path30);
16669
- if (this._watched.has(path30)) {
17771
+ this._closePath(path31);
17772
+ this._addIgnoredPath(path31);
17773
+ if (this._watched.has(path31)) {
16670
17774
  this._addIgnoredPath({
16671
- path: path30,
17775
+ path: path31,
16672
17776
  recursive: true
16673
17777
  });
16674
17778
  }
@@ -16732,38 +17836,38 @@ var FSWatcher = class extends EventEmitter2 {
16732
17836
  * @param stats arguments to be passed with event
16733
17837
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
16734
17838
  */
16735
- async _emit(event, path30, stats) {
17839
+ async _emit(event, path31, stats) {
16736
17840
  if (this.closed)
16737
17841
  return;
16738
17842
  const opts = this.options;
16739
17843
  if (isWindows)
16740
- path30 = sp2.normalize(path30);
17844
+ path31 = sp2.normalize(path31);
16741
17845
  if (opts.cwd)
16742
- path30 = sp2.relative(opts.cwd, path30);
16743
- const args = [path30];
17846
+ path31 = sp2.relative(opts.cwd, path31);
17847
+ const args = [path31];
16744
17848
  if (stats != null)
16745
17849
  args.push(stats);
16746
17850
  const awf = opts.awaitWriteFinish;
16747
17851
  let pw;
16748
- if (awf && (pw = this._pendingWrites.get(path30))) {
17852
+ if (awf && (pw = this._pendingWrites.get(path31))) {
16749
17853
  pw.lastChange = /* @__PURE__ */ new Date();
16750
17854
  return this;
16751
17855
  }
16752
17856
  if (opts.atomic) {
16753
17857
  if (event === EVENTS.UNLINK) {
16754
- this._pendingUnlinks.set(path30, [event, ...args]);
17858
+ this._pendingUnlinks.set(path31, [event, ...args]);
16755
17859
  setTimeout(() => {
16756
- this._pendingUnlinks.forEach((entry, path31) => {
17860
+ this._pendingUnlinks.forEach((entry, path32) => {
16757
17861
  this.emit(...entry);
16758
17862
  this.emit(EVENTS.ALL, ...entry);
16759
- this._pendingUnlinks.delete(path31);
17863
+ this._pendingUnlinks.delete(path32);
16760
17864
  });
16761
17865
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
16762
17866
  return this;
16763
17867
  }
16764
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path30)) {
17868
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
16765
17869
  event = EVENTS.CHANGE;
16766
- this._pendingUnlinks.delete(path30);
17870
+ this._pendingUnlinks.delete(path31);
16767
17871
  }
16768
17872
  }
16769
17873
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -16781,16 +17885,16 @@ var FSWatcher = class extends EventEmitter2 {
16781
17885
  this.emitWithAll(event, args);
16782
17886
  }
16783
17887
  };
16784
- this._awaitWriteFinish(path30, awf.stabilityThreshold, event, awfEmit);
17888
+ this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
16785
17889
  return this;
16786
17890
  }
16787
17891
  if (event === EVENTS.CHANGE) {
16788
- const isThrottled = !this._throttle(EVENTS.CHANGE, path30, 50);
17892
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
16789
17893
  if (isThrottled)
16790
17894
  return this;
16791
17895
  }
16792
17896
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
16793
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path30) : path30;
17897
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path31) : path31;
16794
17898
  let stats2;
16795
17899
  try {
16796
17900
  stats2 = await stat3(fullPath);
@@ -16821,23 +17925,23 @@ var FSWatcher = class extends EventEmitter2 {
16821
17925
  * @param timeout duration of time to suppress duplicate actions
16822
17926
  * @returns tracking object or false if action should be suppressed
16823
17927
  */
16824
- _throttle(actionType, path30, timeout) {
17928
+ _throttle(actionType, path31, timeout) {
16825
17929
  if (!this._throttled.has(actionType)) {
16826
17930
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
16827
17931
  }
16828
17932
  const action = this._throttled.get(actionType);
16829
17933
  if (!action)
16830
17934
  throw new Error("invalid throttle");
16831
- const actionPath = action.get(path30);
17935
+ const actionPath = action.get(path31);
16832
17936
  if (actionPath) {
16833
17937
  actionPath.count++;
16834
17938
  return false;
16835
17939
  }
16836
17940
  let timeoutObject;
16837
17941
  const clear = () => {
16838
- const item = action.get(path30);
17942
+ const item = action.get(path31);
16839
17943
  const count = item ? item.count : 0;
16840
- action.delete(path30);
17944
+ action.delete(path31);
16841
17945
  clearTimeout(timeoutObject);
16842
17946
  if (item)
16843
17947
  clearTimeout(item.timeoutObject);
@@ -16845,7 +17949,7 @@ var FSWatcher = class extends EventEmitter2 {
16845
17949
  };
16846
17950
  timeoutObject = setTimeout(clear, timeout);
16847
17951
  const thr = { timeoutObject, clear, count: 0 };
16848
- action.set(path30, thr);
17952
+ action.set(path31, thr);
16849
17953
  return thr;
16850
17954
  }
16851
17955
  _incrReadyCount() {
@@ -16859,44 +17963,44 @@ var FSWatcher = class extends EventEmitter2 {
16859
17963
  * @param event
16860
17964
  * @param awfEmit Callback to be called when ready for event to be emitted.
16861
17965
  */
16862
- _awaitWriteFinish(path30, threshold, event, awfEmit) {
17966
+ _awaitWriteFinish(path31, threshold, event, awfEmit) {
16863
17967
  const awf = this.options.awaitWriteFinish;
16864
17968
  if (typeof awf !== "object")
16865
17969
  return;
16866
17970
  const pollInterval = awf.pollInterval;
16867
17971
  let timeoutHandler;
16868
- let fullPath = path30;
16869
- if (this.options.cwd && !sp2.isAbsolute(path30)) {
16870
- fullPath = sp2.join(this.options.cwd, path30);
17972
+ let fullPath = path31;
17973
+ if (this.options.cwd && !sp2.isAbsolute(path31)) {
17974
+ fullPath = sp2.join(this.options.cwd, path31);
16871
17975
  }
16872
17976
  const now2 = /* @__PURE__ */ new Date();
16873
17977
  const writes = this._pendingWrites;
16874
17978
  function awaitWriteFinishFn(prevStat) {
16875
17979
  statcb(fullPath, (err, curStat) => {
16876
- if (err || !writes.has(path30)) {
17980
+ if (err || !writes.has(path31)) {
16877
17981
  if (err && err.code !== "ENOENT")
16878
17982
  awfEmit(err);
16879
17983
  return;
16880
17984
  }
16881
17985
  const now3 = Number(/* @__PURE__ */ new Date());
16882
17986
  if (prevStat && curStat.size !== prevStat.size) {
16883
- writes.get(path30).lastChange = now3;
17987
+ writes.get(path31).lastChange = now3;
16884
17988
  }
16885
- const pw = writes.get(path30);
17989
+ const pw = writes.get(path31);
16886
17990
  const df = now3 - pw.lastChange;
16887
17991
  if (df >= threshold) {
16888
- writes.delete(path30);
17992
+ writes.delete(path31);
16889
17993
  awfEmit(void 0, curStat);
16890
17994
  } else {
16891
17995
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
16892
17996
  }
16893
17997
  });
16894
17998
  }
16895
- if (!writes.has(path30)) {
16896
- writes.set(path30, {
17999
+ if (!writes.has(path31)) {
18000
+ writes.set(path31, {
16897
18001
  lastChange: now2,
16898
18002
  cancelWait: () => {
16899
- writes.delete(path30);
18003
+ writes.delete(path31);
16900
18004
  clearTimeout(timeoutHandler);
16901
18005
  return event;
16902
18006
  }
@@ -16907,8 +18011,8 @@ var FSWatcher = class extends EventEmitter2 {
16907
18011
  /**
16908
18012
  * Determines whether user has asked to ignore this path.
16909
18013
  */
16910
- _isIgnored(path30, stats) {
16911
- if (this.options.atomic && DOT_RE.test(path30))
18014
+ _isIgnored(path31, stats) {
18015
+ if (this.options.atomic && DOT_RE.test(path31))
16912
18016
  return true;
16913
18017
  if (!this._userIgnored) {
16914
18018
  const { cwd } = this.options;
@@ -16918,17 +18022,17 @@ var FSWatcher = class extends EventEmitter2 {
16918
18022
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
16919
18023
  this._userIgnored = anymatch(list, void 0);
16920
18024
  }
16921
- return this._userIgnored(path30, stats);
18025
+ return this._userIgnored(path31, stats);
16922
18026
  }
16923
- _isntIgnored(path30, stat5) {
16924
- return !this._isIgnored(path30, stat5);
18027
+ _isntIgnored(path31, stat5) {
18028
+ return !this._isIgnored(path31, stat5);
16925
18029
  }
16926
18030
  /**
16927
18031
  * Provides a set of common helpers and properties relating to symlink handling.
16928
18032
  * @param path file or directory pattern being watched
16929
18033
  */
16930
- _getWatchHelpers(path30) {
16931
- return new WatchHelper(path30, this.options.followSymlinks, this);
18034
+ _getWatchHelpers(path31) {
18035
+ return new WatchHelper(path31, this.options.followSymlinks, this);
16932
18036
  }
16933
18037
  // Directory helpers
16934
18038
  // -----------------
@@ -16960,63 +18064,63 @@ var FSWatcher = class extends EventEmitter2 {
16960
18064
  * @param item base path of item/directory
16961
18065
  */
16962
18066
  _remove(directory, item, isDirectory) {
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))
18067
+ const path31 = sp2.join(directory, item);
18068
+ const fullPath = sp2.resolve(path31);
18069
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
18070
+ if (!this._throttle("remove", path31, 100))
16967
18071
  return;
16968
18072
  if (!isDirectory && this._watched.size === 1) {
16969
18073
  this.add(directory, item, true);
16970
18074
  }
16971
- const wp = this._getWatchedDir(path30);
18075
+ const wp = this._getWatchedDir(path31);
16972
18076
  const nestedDirectoryChildren = wp.getChildren();
16973
- nestedDirectoryChildren.forEach((nested) => this._remove(path30, nested));
18077
+ nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
16974
18078
  const parent = this._getWatchedDir(directory);
16975
18079
  const wasTracked = parent.has(item);
16976
18080
  parent.remove(item);
16977
18081
  if (this._symlinkPaths.has(fullPath)) {
16978
18082
  this._symlinkPaths.delete(fullPath);
16979
18083
  }
16980
- let relPath = path30;
18084
+ let relPath = path31;
16981
18085
  if (this.options.cwd)
16982
- relPath = sp2.relative(this.options.cwd, path30);
18086
+ relPath = sp2.relative(this.options.cwd, path31);
16983
18087
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
16984
18088
  const event = this._pendingWrites.get(relPath).cancelWait();
16985
18089
  if (event === EVENTS.ADD)
16986
18090
  return;
16987
18091
  }
16988
- this._watched.delete(path30);
18092
+ this._watched.delete(path31);
16989
18093
  this._watched.delete(fullPath);
16990
18094
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
16991
- if (wasTracked && !this._isIgnored(path30))
16992
- this._emit(eventName, path30);
16993
- this._closePath(path30);
18095
+ if (wasTracked && !this._isIgnored(path31))
18096
+ this._emit(eventName, path31);
18097
+ this._closePath(path31);
16994
18098
  }
16995
18099
  /**
16996
18100
  * Closes all watchers for a path
16997
18101
  */
16998
- _closePath(path30) {
16999
- this._closeFile(path30);
17000
- const dir = sp2.dirname(path30);
17001
- this._getWatchedDir(dir).remove(sp2.basename(path30));
18102
+ _closePath(path31) {
18103
+ this._closeFile(path31);
18104
+ const dir = sp2.dirname(path31);
18105
+ this._getWatchedDir(dir).remove(sp2.basename(path31));
17002
18106
  }
17003
18107
  /**
17004
18108
  * Closes only file-specific watchers
17005
18109
  */
17006
- _closeFile(path30) {
17007
- const closers = this._closers.get(path30);
18110
+ _closeFile(path31) {
18111
+ const closers = this._closers.get(path31);
17008
18112
  if (!closers)
17009
18113
  return;
17010
18114
  closers.forEach((closer) => closer());
17011
- this._closers.delete(path30);
18115
+ this._closers.delete(path31);
17012
18116
  }
17013
- _addPathCloser(path30, closer) {
18117
+ _addPathCloser(path31, closer) {
17014
18118
  if (!closer)
17015
18119
  return;
17016
- let list = this._closers.get(path30);
18120
+ let list = this._closers.get(path31);
17017
18121
  if (!list) {
17018
18122
  list = [];
17019
- this._closers.set(path30, list);
18123
+ this._closers.set(path31, list);
17020
18124
  }
17021
18125
  list.push(closer);
17022
18126
  }
@@ -17046,11 +18150,11 @@ function watch(paths, options = {}) {
17046
18150
  var chokidar_default = { watch, FSWatcher };
17047
18151
 
17048
18152
  // src/watcher/file-watcher.ts
17049
- import * as path23 from "path";
18153
+ import * as path24 from "path";
17050
18154
 
17051
18155
  // src/watcher/native-recursive-watcher.ts
17052
18156
  import { watch as watch2 } from "fs";
17053
- import * as path21 from "path";
18157
+ import * as path22 from "path";
17054
18158
  var NativeRecursiveWatcher = class {
17055
18159
  constructor(root, onChange, options = {}) {
17056
18160
  this.root = root;
@@ -17098,9 +18202,9 @@ var NativeRecursiveWatcher = class {
17098
18202
  toAbsolutePath(filename) {
17099
18203
  if (filename == null) return null;
17100
18204
  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);
18205
+ const absolutePath = path22.resolve(this.root, normalizedFilename);
18206
+ const relativePath = path22.relative(this.root, absolutePath);
18207
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path22.sep}`) || path22.isAbsolute(relativePath);
17104
18208
  return outsideRoot ? null : absolutePath;
17105
18209
  }
17106
18210
  defaultWatchFactory = (root, listener, options) => watch2(root, options, listener);
@@ -17108,16 +18212,16 @@ var NativeRecursiveWatcher = class {
17108
18212
 
17109
18213
  // src/watcher/snapshot.ts
17110
18214
  import * as fsPromises4 from "fs/promises";
17111
- import * as path22 from "path";
18215
+ import * as path23 from "path";
17112
18216
  async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17113
- const normalizedProjectRoot = path22.resolve(projectRoot);
18217
+ const normalizedProjectRoot = path23.resolve(projectRoot);
17114
18218
  const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17115
18219
  const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17116
18220
  const maxDepth = config.indexing?.maxDepth ?? -1;
17117
18221
  const snapshot = /* @__PURE__ */ new Map();
17118
18222
  const unreadablePrefixes = /* @__PURE__ */ new Set();
17119
18223
  const includeFile = async (filePath) => {
17120
- const normalizedPath2 = path22.resolve(filePath);
18224
+ const normalizedPath2 = path23.resolve(filePath);
17121
18225
  if (!shouldIncludeFile(normalizedPath2, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
17122
18226
  const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17123
18227
  if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
@@ -17129,16 +18233,16 @@ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17129
18233
  } catch (error) {
17130
18234
  if (isMissingFsError(error)) return;
17131
18235
  if (isPermissionFsError(error)) {
17132
- unreadablePrefixes.add(path22.resolve(directoryPath));
18236
+ unreadablePrefixes.add(path23.resolve(directoryPath));
17133
18237
  return;
17134
18238
  }
17135
18239
  throw error;
17136
18240
  }
17137
18241
  for (const entry of entries) {
17138
- const fullPath = path22.join(directoryPath, entry.name);
17139
- const relativePath = path22.relative(normalizedProjectRoot, fullPath);
18242
+ const fullPath = path23.join(directoryPath, entry.name);
18243
+ const relativePath = path23.relative(normalizedProjectRoot, fullPath);
17140
18244
  if (entry.isDirectory()) {
17141
- if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
18245
+ if (hasFilteredPathSegment(relativePath, path23.sep) || isRestrictedDirectory(relativePath, path23.sep)) continue;
17142
18246
  if (ignoreFilter.ignores(relativePath)) continue;
17143
18247
  if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17144
18248
  } else if (entry.isFile()) {
@@ -17151,19 +18255,19 @@ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17151
18255
  return { entries: snapshot, unreadablePrefixes };
17152
18256
  }
17153
18257
  async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
17154
- const normalizedProjectRoot = path22.resolve(projectRoot);
17155
- const normalizedTargetPath = path22.resolve(targetPath);
18258
+ const normalizedProjectRoot = path23.resolve(projectRoot);
18259
+ const normalizedTargetPath = path23.resolve(targetPath);
17156
18260
  if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
17157
18261
  return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
17158
18262
  }
17159
18263
  const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17160
18264
  const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17161
18265
  const maxDepth = config.indexing?.maxDepth ?? -1;
17162
- const explicitConfigPaths = new Set(configPaths.map((configPath) => path22.resolve(configPath)));
18266
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path23.resolve(configPath)));
17163
18267
  const snapshot = /* @__PURE__ */ new Map();
17164
18268
  const unreadablePrefixes = /* @__PURE__ */ new Set();
17165
18269
  const includeFile = async (filePath) => {
17166
- const normalizedPath2 = path22.resolve(filePath);
18270
+ const normalizedPath2 = path23.resolve(filePath);
17167
18271
  if (!explicitConfigPaths.has(normalizedPath2) && !shouldIncludeFile(
17168
18272
  normalizedPath2,
17169
18273
  normalizedProjectRoot,
@@ -17181,16 +18285,16 @@ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, ta
17181
18285
  } catch (error) {
17182
18286
  if (isMissingFsError(error)) return;
17183
18287
  if (isPermissionFsError(error)) {
17184
- unreadablePrefixes.add(path22.resolve(directoryPath));
18288
+ unreadablePrefixes.add(path23.resolve(directoryPath));
17185
18289
  return;
17186
18290
  }
17187
18291
  throw error;
17188
18292
  }
17189
18293
  for (const entry of entries) {
17190
- const fullPath = path22.join(directoryPath, entry.name);
17191
- const relativePath = path22.relative(normalizedProjectRoot, fullPath);
18294
+ const fullPath = path23.join(directoryPath, entry.name);
18295
+ const relativePath = path23.relative(normalizedProjectRoot, fullPath);
17192
18296
  if (entry.isDirectory()) {
17193
- if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
18297
+ if (hasFilteredPathSegment(relativePath, path23.sep) || isRestrictedDirectory(relativePath, path23.sep)) continue;
17194
18298
  if (ignoreFilter.ignores(relativePath)) continue;
17195
18299
  if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17196
18300
  } else if (entry.isFile()) {
@@ -17214,7 +18318,7 @@ function completeFileSnapshot(previous, scan) {
17214
18318
  return completed;
17215
18319
  }
17216
18320
  async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
17217
- for (const configPath of [...new Set(configPaths.map((value) => path22.resolve(value)))]) {
18321
+ for (const configPath of [...new Set(configPaths.map((value) => path23.resolve(value)))]) {
17218
18322
  if (snapshot.has(configPath)) continue;
17219
18323
  const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
17220
18324
  if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
@@ -17224,12 +18328,12 @@ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, co
17224
18328
  await includeExplicitConfigPaths(
17225
18329
  snapshot,
17226
18330
  unreadablePrefixes,
17227
- configPaths.filter((configPath) => isWithinPath(targetPath, path22.resolve(configPath)))
18331
+ configPaths.filter((configPath) => isWithinPath(targetPath, path23.resolve(configPath)))
17228
18332
  );
17229
18333
  }
17230
18334
  function isWithinPath(parentPath, childPath) {
17231
- const relativePath = path22.relative(parentPath, childPath);
17232
- return relativePath === "" || !relativePath.startsWith(`..${path22.sep}`) && relativePath !== ".." && !path22.isAbsolute(relativePath);
18335
+ const relativePath = path23.relative(parentPath, childPath);
18336
+ return relativePath === "" || !relativePath.startsWith(`..${path23.sep}`) && relativePath !== ".." && !path23.isAbsolute(relativePath);
17233
18337
  }
17234
18338
  async function readStatIfFile(filePath, unreadablePrefixes) {
17235
18339
  try {
@@ -17238,7 +18342,7 @@ async function readStatIfFile(filePath, unreadablePrefixes) {
17238
18342
  } catch (error) {
17239
18343
  if (isMissingFsError(error)) return null;
17240
18344
  if (isPermissionFsError(error)) {
17241
- unreadablePrefixes.add(path22.resolve(filePath));
18345
+ unreadablePrefixes.add(path23.resolve(filePath));
17242
18346
  return null;
17243
18347
  }
17244
18348
  throw error;
@@ -17375,8 +18479,8 @@ var FileWatcher = class {
17375
18479
  this.createWatcher();
17376
18480
  }
17377
18481
  resetReady() {
17378
- this.readyPromise = new Promise((resolve17) => {
17379
- this.resolveReady = resolve17;
18482
+ this.readyPromise = new Promise((resolve18) => {
18483
+ this.resolveReady = resolve18;
17380
18484
  });
17381
18485
  this.startupReadySignals = 1;
17382
18486
  }
@@ -17407,7 +18511,7 @@ var FileWatcher = class {
17407
18511
  const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
17408
18512
  const watcherOptions = {
17409
18513
  ignored: (filePath) => {
17410
- const relativePath = path23.relative(this.projectRoot, filePath);
18514
+ const relativePath = path24.relative(this.projectRoot, filePath);
17411
18515
  if (!relativePath) return false;
17412
18516
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
17413
18517
  return false;
@@ -17415,10 +18519,10 @@ var FileWatcher = class {
17415
18519
  if (this.isOutsideProjectPath(relativePath)) {
17416
18520
  return true;
17417
18521
  }
17418
- if (hasFilteredPathSegment(relativePath, path23.sep)) {
18522
+ if (hasFilteredPathSegment(relativePath, path24.sep)) {
17419
18523
  return true;
17420
18524
  }
17421
- if (isRestrictedDirectory(relativePath, path23.sep)) {
18525
+ if (isRestrictedDirectory(relativePath, path24.sep)) {
17422
18526
  return true;
17423
18527
  }
17424
18528
  if (ignoreFilter.ignores(relativePath)) {
@@ -17509,13 +18613,13 @@ var FileWatcher = class {
17509
18613
  getExternalConfigWatchTargets() {
17510
18614
  return [...new Set(
17511
18615
  this.projectConfigPaths.filter((projectConfigPath) => {
17512
- const relativeConfigPath = path23.relative(this.projectRoot, projectConfigPath);
18616
+ const relativeConfigPath = path24.relative(this.projectRoot, projectConfigPath);
17513
18617
  return this.isOutsideProjectPath(relativeConfigPath);
17514
18618
  }).map((projectConfigPath) => {
17515
- if (existsSync13(projectConfigPath)) {
18619
+ if (existsSync14(projectConfigPath)) {
17516
18620
  return projectConfigPath;
17517
18621
  }
17518
- return this.getNearestExistingDirectory(path23.dirname(projectConfigPath));
18622
+ return this.getNearestExistingDirectory(path24.dirname(projectConfigPath));
17519
18623
  })
17520
18624
  )];
17521
18625
  }
@@ -17577,7 +18681,7 @@ var FileWatcher = class {
17577
18681
  }
17578
18682
  scheduleNativeReconciliation(generation, filePath) {
17579
18683
  if (!this.isCurrentNativeSetup(generation)) return;
17580
- const requiresFullReconciliation = filePath === path23.join(this.projectRoot, ".gitignore");
18684
+ const requiresFullReconciliation = filePath === path24.join(this.projectRoot, ".gitignore");
17581
18685
  const invalidatedPath = requiresFullReconciliation ? null : filePath;
17582
18686
  this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
17583
18687
  if (this.nativeReconcileTimer) {
@@ -17672,23 +18776,23 @@ var FileWatcher = class {
17672
18776
  this.scheduleFlush();
17673
18777
  }
17674
18778
  isProjectConfigPath(filePath) {
17675
- const relativePath = path23.relative(this.projectRoot, filePath);
17676
- const normalizedRelativePath = path23.normalize(relativePath);
18779
+ const relativePath = path24.relative(this.projectRoot, filePath);
18780
+ const normalizedRelativePath = path24.normalize(relativePath);
17677
18781
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
17678
18782
  }
17679
18783
  isProjectConfigPathOrAncestor(relativePath) {
17680
- const normalizedRelativePath = path23.normalize(relativePath);
18784
+ const normalizedRelativePath = path24.normalize(relativePath);
17681
18785
  return this.getProjectConfigRelativePaths().some(
17682
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path23.sep}`)
18786
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path24.sep}`)
17683
18787
  );
17684
18788
  }
17685
18789
  isOutsideProjectPath(relativePath) {
17686
- return relativePath === ".." || relativePath.startsWith(`..${path23.sep}`) || path23.isAbsolute(relativePath);
18790
+ return relativePath === ".." || relativePath.startsWith(`..${path24.sep}`) || path24.isAbsolute(relativePath);
17687
18791
  }
17688
18792
  getNearestExistingDirectory(directoryPath) {
17689
18793
  let candidate = directoryPath;
17690
- while (!existsSync13(candidate)) {
17691
- const parent = path23.dirname(candidate);
18794
+ while (!existsSync14(candidate)) {
18795
+ const parent = path24.dirname(candidate);
17692
18796
  if (parent === candidate) break;
17693
18797
  candidate = parent;
17694
18798
  }
@@ -17696,7 +18800,7 @@ var FileWatcher = class {
17696
18800
  }
17697
18801
  getProjectConfigRelativePaths() {
17698
18802
  return this.projectConfigPaths.map(
17699
- (configPath) => path23.normalize(path23.relative(this.projectRoot, configPath))
18803
+ (configPath) => path24.normalize(path24.relative(this.projectRoot, configPath))
17700
18804
  );
17701
18805
  }
17702
18806
  getConfigPathStates() {
@@ -17754,7 +18858,7 @@ var FileWatcher = class {
17754
18858
  return;
17755
18859
  }
17756
18860
  const changes = Array.from(this.pendingChanges.entries()).map(
17757
- ([path30, type]) => ({ path: path30, type })
18861
+ ([path31, type]) => ({ path: path31, type })
17758
18862
  );
17759
18863
  this.pendingChanges.clear();
17760
18864
  try {
@@ -17800,7 +18904,7 @@ var FileWatcher = class {
17800
18904
  };
17801
18905
 
17802
18906
  // src/watcher/git-head-watcher.ts
17803
- import * as path24 from "path";
18907
+ import * as path25 from "path";
17804
18908
  var GitHeadWatcher = class {
17805
18909
  watcher = null;
17806
18910
  projectRoot;
@@ -17822,13 +18926,13 @@ var GitHeadWatcher = class {
17822
18926
  this.readyPromise = Promise.resolve();
17823
18927
  return;
17824
18928
  }
17825
- this.readyPromise = new Promise((resolve17) => {
17826
- this.resolveReady = resolve17;
18929
+ this.readyPromise = new Promise((resolve18) => {
18930
+ this.resolveReady = resolve18;
17827
18931
  });
17828
18932
  this.onBranchChange = handler;
17829
18933
  this.currentBranch = getCurrentBranch(this.projectRoot);
17830
18934
  const headPath = getHeadPath(this.projectRoot);
17831
- const refsPath = path24.join(this.projectRoot, ".git", "refs", "heads");
18935
+ const refsPath = path25.join(this.projectRoot, ".git", "refs", "heads");
17832
18936
  this.watcher = chokidar_default.watch([headPath, refsPath], {
17833
18937
  persistent: true,
17834
18938
  ignoreInitial: true,
@@ -17896,7 +19000,9 @@ var GitHeadWatcher = class {
17896
19000
  function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options = {}) {
17897
19001
  const fileWatcher = new FileWatcher(projectRoot, config, host, options);
17898
19002
  const configPaths = getConfigPaths(projectRoot, host, options);
17899
- configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer);
19003
+ configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer, {
19004
+ synchronizeBackgroundWorker: false
19005
+ });
17900
19006
  let stopped = false;
17901
19007
  const requestReindex = () => {
17902
19008
  if (stopped) return;
@@ -17916,7 +19022,9 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options
17916
19022
  const parsedConfig = options.configPath ? parseConfig(loadConfigFile(options.configPath)) : void 0;
17917
19023
  const refreshedConfig = refreshIndexerForDirectory(projectRoot, host, parsedConfig);
17918
19024
  if (refreshedConfig) {
17919
- configureAutoIndex(projectRoot, host, refreshedConfig, getIndexer);
19025
+ configureAutoIndex(projectRoot, host, refreshedConfig, getIndexer, {
19026
+ synchronizeBackgroundWorker: false
19027
+ });
17920
19028
  }
17921
19029
  }
17922
19030
  requestReindex();
@@ -18692,7 +19800,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
18692
19800
  const directory = input.directory ?? void 0;
18693
19801
  const tokenBudget = input.tokenBudget ?? void 0;
18694
19802
  if (from && to) {
18695
- const path30 = await getCallGraphPath(
19803
+ const path31 = await getCallGraphPath(
18696
19804
  projectRoot,
18697
19805
  host,
18698
19806
  from,
@@ -18701,25 +19809,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
18701
19809
  fromFilePath,
18702
19810
  toFilePath
18703
19811
  );
18704
- const pathText = formatCallGraphPathResult(path30);
18705
- if (path30.path.length > 0) {
19812
+ const pathText = formatCallGraphPathResult(path31);
19813
+ if (path31.path.length > 0) {
18706
19814
  const fitted2 = fitTextToContextBudget(
18707
19815
  pathText,
18708
19816
  tokenBudget
18709
19817
  );
18710
19818
  return {
18711
19819
  text: fitted2.text,
18712
- details: fittedDetails("path", fitted2, path30.path.length)
19820
+ details: fittedDetails("path", fitted2, path31.path.length)
18713
19821
  };
18714
19822
  }
18715
- if (path30.from.status !== "resolved" || path30.to.status !== "resolved") {
19823
+ if (path31.from.status !== "resolved" || path31.to.status !== "resolved") {
18716
19824
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
18717
19825
  return {
18718
19826
  text: fitted2.text,
18719
19827
  details: fittedDetails("path", fitted2, 0)
18720
19828
  };
18721
19829
  }
18722
- const resolvedFrom = path30.from;
19830
+ const resolvedFrom = path31.from;
18723
19831
  const { callers } = await getCallGraphData(projectRoot, host, {
18724
19832
  name: to,
18725
19833
  direction: "callers",
@@ -18843,6 +19951,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18843
19951
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18844
19952
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18845
19953
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
19954
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18846
19955
  if (result.kind === "busy") return { text: result.text, isError: true };
18847
19956
  if (result.kind === "message") return { text: result.text };
18848
19957
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -18879,7 +19988,7 @@ async function executeCallGraph(projectRoot, host, args) {
18879
19988
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
18880
19989
  }
18881
19990
  async function executeCallGraphPath(projectRoot, host, args) {
18882
- const path30 = await getCallGraphPath(
19991
+ const path31 = await getCallGraphPath(
18883
19992
  projectRoot,
18884
19993
  host,
18885
19994
  args.from,
@@ -18888,7 +19997,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
18888
19997
  args.fromFilePath,
18889
19998
  args.toFilePath
18890
19999
  );
18891
- return { text: formatCallGraphPathResult(path30) };
20000
+ return { text: formatCallGraphPathResult(path31) };
18892
20001
  }
18893
20002
  async function executeCodeCommunities(projectRoot, host, args) {
18894
20003
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -18896,13 +20005,13 @@ async function executeCodeCommunities(projectRoot, host, args) {
18896
20005
  }
18897
20006
 
18898
20007
  // src/adapters/opencode/tools.ts
18899
- import { writeFileSync as writeFileSync4 } from "fs";
18900
- import * as os7 from "os";
18901
- import * as path27 from "path";
20008
+ import { writeFileSync as writeFileSync5 } from "fs";
20009
+ import * as os8 from "os";
20010
+ import * as path28 from "path";
18902
20011
 
18903
20012
  // src/tools/visualize/activity.ts
18904
20013
  import { execFileSync } from "child_process";
18905
- import * as path25 from "path";
20014
+ import * as path26 from "path";
18906
20015
  function attachRecentActivity(data, projectRoot) {
18907
20016
  const activity = readGitActivity(projectRoot);
18908
20017
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -19064,7 +20173,7 @@ function normalizePath3(filePath) {
19064
20173
  return filePath.replace(/\\/g, "/");
19065
20174
  }
19066
20175
  function toGitRelativePath(projectRoot, filePath) {
19067
- const relativePath = path25.isAbsolute(filePath) ? path25.relative(projectRoot, filePath) : filePath;
20176
+ const relativePath = path26.isAbsolute(filePath) ? path26.relative(projectRoot, filePath) : filePath;
19068
20177
  return normalizePath3(relativePath);
19069
20178
  }
19070
20179
 
@@ -19322,7 +20431,7 @@ render();
19322
20431
  }
19323
20432
 
19324
20433
  // src/tools/visualize/transform.ts
19325
- import * as path26 from "path";
20434
+ import * as path27 from "path";
19326
20435
 
19327
20436
  // src/tools/visualize/modules.ts
19328
20437
  var MAX_MODULES = 18;
@@ -19582,7 +20691,7 @@ function transformForVisualization(symbols, edges, options = {}) {
19582
20691
  filePath: s.filePath,
19583
20692
  kind: s.kind,
19584
20693
  line: s.startLine,
19585
- directory: path26.dirname(s.filePath),
20694
+ directory: path27.dirname(s.filePath),
19586
20695
  moduleId: "",
19587
20696
  moduleLabel: ""
19588
20697
  }));
@@ -19707,6 +20816,7 @@ var index_codebase = tool({
19707
20816
  args: {
19708
20817
  force: z3.boolean().optional().default(false).describe("Force reindex even if already indexed"),
19709
20818
  estimateOnly: z3.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
20819
+ dryRun: z3.boolean().optional().default(false).describe("Parse the file set and report the exact embedding token total without indexing. Read-only; the index is not changed. The total is the value 'Tokens used' climbs to for a force index (and an upper bound for an incremental)."),
19710
20820
  verbose: z3.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
19711
20821
  },
19712
20822
  async execute(args, context) {
@@ -19910,8 +21020,8 @@ var index_visualize = tool({
19910
21020
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
19911
21021
  }
19912
21022
  const html = generateVisualizationHtml(vizData);
19913
- const outputPath = path27.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
19914
- writeFileSync4(outputPath, html, "utf-8");
21023
+ const outputPath = path28.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
21024
+ writeFileSync5(outputPath, html, "utf-8");
19915
21025
  let result = `Temporal call graph visualization generated: ${outputPath}
19916
21026
 
19917
21027
  `;
@@ -20022,8 +21132,8 @@ var MCP_TOOL_NAMES = [
20022
21132
  ];
20023
21133
 
20024
21134
  // src/commands/loader.ts
20025
- import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
20026
- import * as path28 from "path";
21135
+ import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
21136
+ import * as path29 from "path";
20027
21137
  function parseFrontmatter(content) {
20028
21138
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
20029
21139
  const match = content.match(frontmatterRegex);
@@ -20044,21 +21154,21 @@ function parseFrontmatter(content) {
20044
21154
  }
20045
21155
  function loadCommandsFromDirectory(commandsDir) {
20046
21156
  const commands = /* @__PURE__ */ new Map();
20047
- if (!existsSync14(commandsDir)) {
21157
+ if (!existsSync15(commandsDir)) {
20048
21158
  return commands;
20049
21159
  }
20050
21160
  const files = readdirSync3(commandsDir).filter((f) => f.endsWith(".md"));
20051
21161
  for (const file of files) {
20052
- const filePath = path28.join(commandsDir, file);
21162
+ const filePath = path29.join(commandsDir, file);
20053
21163
  let content;
20054
21164
  try {
20055
- content = readFileSync9(filePath, "utf-8");
21165
+ content = readFileSync10(filePath, "utf-8");
20056
21166
  } catch (error) {
20057
21167
  const message = error instanceof Error ? error.message : String(error);
20058
21168
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
20059
21169
  }
20060
21170
  const { frontmatter, body } = parseFrontmatter(content);
20061
- const name = path28.basename(file, ".md");
21171
+ const name = path29.basename(file, ".md");
20062
21172
  const description = frontmatter.description || `Run the ${name} command`;
20063
21173
  commands.set(name, {
20064
21174
  description,
@@ -20307,6 +21417,7 @@ function assessRoutingIntent(text) {
20307
21417
  };
20308
21418
  }
20309
21419
  function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
21420
+ const hasSymbolCue = hasIdentifierShape(assessment.text) || containsQuotedIdentifier(assessment.text);
20310
21421
  if (assessment.intent === "definition_lookup") {
20311
21422
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20312
21423
  return "For this turn, if you need a symbol definition, check `index_status` first and run `index_codebase` if the index is missing or incompatible. Then use `implementation_lookup` for the definition site. Use `grep` for exhaustive literal matches.";
@@ -20316,12 +21427,13 @@ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20316
21427
  if (assessment.intent !== "local_conceptual" && assessment.intent !== "local_broad_task") {
20317
21428
  return null;
20318
21429
  }
21430
+ const preEditHint = assessment.intent === "local_broad_task" && hasSymbolCue ? " If a likely target symbol is already known or strongly suspected, consider optional `codebase_edit_context` as a compact pre-edit next step for bounded source plus direct callers and callees." : "";
20319
21431
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20320
21432
  const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
20321
- return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Then use \`codebase_context\` as the first local repository lookup. Use \`grep\` for exact identifiers or exhaustive matches.`;
21433
+ return `For this turn, if local code discovery by behavior is needed, check \`index_status\` first and run \`index_codebase\` if the index is missing or incompatible.${graphHandoff2} Then use \`codebase_context\` as the first local repository lookup. Use \`grep\` for exact identifiers or exhaustive matches.${preEditHint}`;
20322
21434
  }
20323
21435
  const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
20324
- return `For this turn, prefer \`codebase_context\` for local code discovery, then use \`codebase_peek\` for metadata and \`codebase_search\` when you need implementation content${graphHandoff}. Use \`grep\` for exact identifiers or exhaustive matches.`;
21436
+ return `For this turn, prefer \`codebase_context\` for local code discovery, then use \`codebase_peek\` for metadata and \`codebase_search\` when you need implementation content${graphHandoff}. Use \`grep\` for exact identifiers or exhaustive matches.${preEditHint}`;
20325
21437
  }
20326
21438
  var RoutingHintController = class {
20327
21439
  constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
@@ -20363,7 +21475,7 @@ var RoutingHintController = class {
20363
21475
  if (!state || !state.pendingHint) {
20364
21476
  return;
20365
21477
  }
20366
- if (toolName === "codebase_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
21478
+ if (toolName === "codebase_context" || toolName === "codebase_edit_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20367
21479
  state.pendingHint = false;
20368
21480
  state.updatedAt = Date.now();
20369
21481
  this.sessionState.set(sessionID, state);
@@ -20391,42 +21503,13 @@ var RoutingHintController = class {
20391
21503
  };
20392
21504
 
20393
21505
  // src/adapters/opencode.ts
20394
- var activeWatchers = /* @__PURE__ */ new Map();
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
- }
20421
- }
20422
- }
20423
21506
  function getCommandsDir() {
20424
21507
  let currentDir = process.cwd();
20425
21508
  if (typeof import.meta !== "undefined" && import.meta.url) {
20426
- currentDir = path29.dirname(fileURLToPath2(import.meta.url));
21509
+ currentDir = path30.dirname(fileURLToPath2(import.meta.url));
20427
21510
  }
20428
- const packageRoot = path29.basename(currentDir) === "adapters" ? path29.join(currentDir, "..", "..") : path29.join(currentDir, "..");
20429
- return path29.join(packageRoot, "commands");
21511
+ const packageRoot = path30.basename(currentDir) === "adapters" ? path30.join(currentDir, "..", "..") : path30.join(currentDir, "..");
21512
+ return path30.join(packageRoot, "commands");
20430
21513
  }
20431
21514
  function appendRoutingHints(output, hints, preferredRole) {
20432
21515
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -20452,8 +21535,9 @@ var plugin = async ({ directory, worktree }) => {
20452
21535
  initializeTools2(projectRoot, config);
20453
21536
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
20454
21537
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
20455
- const isHomeDir = isHomeDirectory(projectRoot);
20456
- const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
21538
+ const projectSafety = getProjectSafety(projectRoot, config);
21539
+ const isHomeDir = projectSafety.blockedReason === "home-directory";
21540
+ const isValidProject = projectSafety.safeToRun;
20457
21541
  if (isHomeDir) {
20458
21542
  console.warn(
20459
21543
  `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
@@ -20463,16 +21547,24 @@ var plugin = async ({ directory, worktree }) => {
20463
21547
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
20464
21548
  );
20465
21549
  }
20466
- if (config.indexing.autoIndex && isValidProject) {
20467
- startAutoIndex(projectRoot, "opencode", "startup");
20468
- }
20469
- if (config.indexing.watchFiles && isValidProject) {
20470
- await replaceActiveWatcher(
20471
- projectRoot,
20472
- () => createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode")
20473
- );
21550
+ if (!isValidProject) {
21551
+ await stopBackgroundWorker(projectRoot, "opencode").catch((error) => {
21552
+ console.error("[codebase-index] Failed to stop unsafe OpenCode background worker:", error);
21553
+ });
20474
21554
  } else {
20475
- await replaceActiveWatcher(projectRoot, null);
21555
+ const watcherFactoryForConfig = (refreshedConfig) => refreshedConfig.indexing.watchFiles ? () => createWatcherWithIndexer(getProjectIndexer, projectRoot, refreshedConfig, "opencode") : null;
21556
+ configureBackgroundWorker(projectRoot, "opencode", config, {
21557
+ startAutoIndex: (source, allowDisabledAutoIndex) => {
21558
+ startAutoIndexForBackgroundWorker(projectRoot, "opencode", source, allowDisabledAutoIndex);
21559
+ },
21560
+ stopAutoIndex: () => stopAutoIndexForBackgroundWorker(projectRoot, "opencode"),
21561
+ watcherFactory: watcherFactoryForConfig(config),
21562
+ watcherFactoryForConfig,
21563
+ replaceWatcher: true
21564
+ }, {
21565
+ restartAutoIndex: true
21566
+ });
21567
+ await waitForBackgroundWorkerStart(projectRoot, "opencode");
20476
21568
  }
20477
21569
  return {
20478
21570
  tool: {