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.cjs CHANGED
@@ -333,7 +333,7 @@ var require_ignore = __commonJS({
333
333
  // path matching.
334
334
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
335
335
  // @returns {TestResult} true if a file is ignored
336
- test(path30, checkUnignored, mode) {
336
+ test(path31, checkUnignored, mode) {
337
337
  let ignored = false;
338
338
  let unignored = false;
339
339
  let matchedRule;
@@ -342,7 +342,7 @@ var require_ignore = __commonJS({
342
342
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
343
343
  return;
344
344
  }
345
- const matched = rule[mode].test(path30);
345
+ const matched = rule[mode].test(path31);
346
346
  if (!matched) {
347
347
  return;
348
348
  }
@@ -363,17 +363,17 @@ var require_ignore = __commonJS({
363
363
  var throwError = (message, Ctor) => {
364
364
  throw new Ctor(message);
365
365
  };
366
- var checkPath = (path30, originalPath, doThrow) => {
367
- if (!isString(path30)) {
366
+ var checkPath = (path31, originalPath, doThrow) => {
367
+ if (!isString(path31)) {
368
368
  return doThrow(
369
369
  `path must be a string, but got \`${originalPath}\``,
370
370
  TypeError
371
371
  );
372
372
  }
373
- if (!path30) {
373
+ if (!path31) {
374
374
  return doThrow(`path must not be empty`, TypeError);
375
375
  }
376
- if (checkPath.isNotRelative(path30)) {
376
+ if (checkPath.isNotRelative(path31)) {
377
377
  const r = "`path.relative()`d";
378
378
  return doThrow(
379
379
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -382,7 +382,7 @@ var require_ignore = __commonJS({
382
382
  }
383
383
  return true;
384
384
  };
385
- var isNotRelative = (path30) => REGEX_TEST_INVALID_PATH.test(path30);
385
+ var isNotRelative = (path31) => REGEX_TEST_INVALID_PATH.test(path31);
386
386
  checkPath.isNotRelative = isNotRelative;
387
387
  checkPath.convert = (p) => p;
388
388
  var Ignore2 = class {
@@ -412,19 +412,19 @@ var require_ignore = __commonJS({
412
412
  }
413
413
  // @returns {TestResult}
414
414
  _test(originalPath, cache, checkUnignored, slices) {
415
- const path30 = originalPath && checkPath.convert(originalPath);
415
+ const path31 = originalPath && checkPath.convert(originalPath);
416
416
  checkPath(
417
- path30,
417
+ path31,
418
418
  originalPath,
419
419
  this._strictPathCheck ? throwError : RETURN_FALSE
420
420
  );
421
- return this._t(path30, cache, checkUnignored, slices);
421
+ return this._t(path31, cache, checkUnignored, slices);
422
422
  }
423
- checkIgnore(path30) {
424
- if (!REGEX_TEST_TRAILING_SLASH.test(path30)) {
425
- return this.test(path30);
423
+ checkIgnore(path31) {
424
+ if (!REGEX_TEST_TRAILING_SLASH.test(path31)) {
425
+ return this.test(path31);
426
426
  }
427
- const slices = path30.split(SLASH2).filter(Boolean);
427
+ const slices = path31.split(SLASH2).filter(Boolean);
428
428
  slices.pop();
429
429
  if (slices.length) {
430
430
  const parent = this._t(
@@ -437,18 +437,18 @@ var require_ignore = __commonJS({
437
437
  return parent;
438
438
  }
439
439
  }
440
- return this._rules.test(path30, false, MODE_CHECK_IGNORE);
440
+ return this._rules.test(path31, false, MODE_CHECK_IGNORE);
441
441
  }
442
- _t(path30, cache, checkUnignored, slices) {
443
- if (path30 in cache) {
444
- return cache[path30];
442
+ _t(path31, cache, checkUnignored, slices) {
443
+ if (path31 in cache) {
444
+ return cache[path31];
445
445
  }
446
446
  if (!slices) {
447
- slices = path30.split(SLASH2).filter(Boolean);
447
+ slices = path31.split(SLASH2).filter(Boolean);
448
448
  }
449
449
  slices.pop();
450
450
  if (!slices.length) {
451
- return cache[path30] = this._rules.test(path30, checkUnignored, MODE_IGNORE);
451
+ return cache[path31] = this._rules.test(path31, checkUnignored, MODE_IGNORE);
452
452
  }
453
453
  const parent = this._t(
454
454
  slices.join(SLASH2) + SLASH2,
@@ -456,29 +456,29 @@ var require_ignore = __commonJS({
456
456
  checkUnignored,
457
457
  slices
458
458
  );
459
- return cache[path30] = parent.ignored ? parent : this._rules.test(path30, checkUnignored, MODE_IGNORE);
459
+ return cache[path31] = parent.ignored ? parent : this._rules.test(path31, checkUnignored, MODE_IGNORE);
460
460
  }
461
- ignores(path30) {
462
- return this._test(path30, this._ignoreCache, false).ignored;
461
+ ignores(path31) {
462
+ return this._test(path31, this._ignoreCache, false).ignored;
463
463
  }
464
464
  createFilter() {
465
- return (path30) => !this.ignores(path30);
465
+ return (path31) => !this.ignores(path31);
466
466
  }
467
467
  filter(paths) {
468
468
  return makeArray(paths).filter(this.createFilter());
469
469
  }
470
470
  // @returns {TestResult}
471
- test(path30) {
472
- return this._test(path30, this._testCache, true);
471
+ test(path31) {
472
+ return this._test(path31, this._testCache, true);
473
473
  }
474
474
  };
475
475
  var factory = (options) => new Ignore2(options);
476
- var isPathValid = (path30) => checkPath(path30 && checkPath.convert(path30), path30, RETURN_FALSE);
476
+ var isPathValid = (path31) => checkPath(path31 && checkPath.convert(path31), path31, RETURN_FALSE);
477
477
  var setupWindows = () => {
478
478
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
479
479
  checkPath.convert = makePosix;
480
480
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
481
- checkPath.isNotRelative = (path30) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path30) || isNotRelative(path30);
481
+ checkPath.isNotRelative = (path31) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path31) || isNotRelative(path31);
482
482
  };
483
483
  if (
484
484
  // Detect `process` so that it can run in browsers.
@@ -663,7 +663,7 @@ __export(index_exports, {
663
663
  module.exports = __toCommonJS(index_exports);
664
664
 
665
665
  // src/adapters/opencode.ts
666
- var path29 = __toESM(require("path"), 1);
666
+ var path30 = __toESM(require("path"), 1);
667
667
  var import_url = require("url");
668
668
 
669
669
  // src/config/constants.ts
@@ -1762,7 +1762,7 @@ function loadMergedConfig(projectRoot, host) {
1762
1762
 
1763
1763
  // src/tools/operations.ts
1764
1764
  var import_fs13 = require("fs");
1765
- var path20 = __toESM(require("path"), 1);
1765
+ var path21 = __toESM(require("path"), 1);
1766
1766
 
1767
1767
  // src/tools/knowledge-base-paths.ts
1768
1768
  var path8 = __toESM(require("path"), 1);
@@ -2611,8 +2611,8 @@ function formatExactSearchHandoff(results) {
2611
2611
  }
2612
2612
  function formatContextEvidence(result, index) {
2613
2613
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
2614
- const path30 = compactEvidenceValue(result.filePath, 120);
2615
- return `[${index}] ${result.chunkType}${symbol} in ${path30}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2614
+ const path31 = compactEvidenceValue(result.filePath, 120);
2615
+ return `[${index}] ${result.chunkType}${symbol} in ${path31}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
2616
2616
  }
2617
2617
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
2618
2618
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -3255,8 +3255,8 @@ function formatEffectivenessMetrics(snapshot) {
3255
3255
 
3256
3256
  // src/utils/auto-index.ts
3257
3257
  var import_fs7 = require("fs");
3258
- var os3 = __toESM(require("os"), 1);
3259
- var path11 = __toESM(require("path"), 1);
3258
+ var os4 = __toESM(require("os"), 1);
3259
+ var path12 = __toESM(require("path"), 1);
3260
3260
 
3261
3261
  // src/indexer/index-lock.ts
3262
3262
  var import_crypto = require("crypto");
@@ -3503,7 +3503,7 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
3503
3503
  return true;
3504
3504
  }
3505
3505
  function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
3506
- const reclaimPath = path9.join(lockPath, RECLAIM_DIRECTORY_NAME);
3506
+ const reclaimPath2 = path9.join(lockPath, RECLAIM_DIRECTORY_NAME);
3507
3507
  const reclaimOwner = {
3508
3508
  pid: process.pid,
3509
3509
  hostname: os2.hostname(),
@@ -3512,19 +3512,19 @@ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
3512
3512
  expectedOwnerToken: expectedOwner.token
3513
3513
  };
3514
3514
  for (let attempt = 0; attempt < 2; attempt += 1) {
3515
- if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
3515
+ if (publishJsonDirectory(reclaimPath2, reclaimOwner)) break;
3516
3516
  if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
3517
3517
  return false;
3518
3518
  }
3519
3519
  try {
3520
- const currentReclaimer = readReclaimOwner(reclaimPath);
3520
+ const currentReclaimer = readReclaimOwner(reclaimPath2);
3521
3521
  const currentOwner = readDirectoryOwner(lockPath);
3522
3522
  if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
3523
3523
  return false;
3524
3524
  }
3525
3525
  publishRecoveryMarker(indexPath, expectedOwner);
3526
3526
  const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
3527
- const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
3527
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath2);
3528
3528
  if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
3529
3529
  return false;
3530
3530
  }
@@ -3627,77 +3627,951 @@ function releaseIndexLock(lease) {
3627
3627
  }
3628
3628
  return false;
3629
3629
  }
3630
- try {
3631
- retryTransientFilesystemOperation(() => (0, import_fs5.rmSync)(releasePath, { recursive: true, force: true }));
3632
- } catch (error) {
3633
- console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
3630
+ try {
3631
+ retryTransientFilesystemOperation(() => (0, import_fs5.rmSync)(releasePath, { recursive: true, force: true }));
3632
+ } catch (error) {
3633
+ console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
3634
+ }
3635
+ return true;
3636
+ }
3637
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
3638
+ const currentOwner = readDirectoryOwner(lease.lockPath);
3639
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
3640
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
3641
+ }
3642
+ const nextOwner = { ...currentOwner };
3643
+ if (clearRecovery === null) {
3644
+ delete nextOwner.clearRecovery;
3645
+ } else {
3646
+ nextOwner.clearRecovery = clearRecovery;
3647
+ }
3648
+ const ownerPath = path9.join(lease.lockPath, OWNER_FILE_NAME);
3649
+ const temporaryPath = path9.join(
3650
+ lease.lockPath,
3651
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${(0, import_crypto.randomUUID)()}`
3652
+ );
3653
+ try {
3654
+ (0, import_fs5.writeFileSync)(temporaryPath, JSON.stringify(nextOwner), {
3655
+ encoding: "utf-8",
3656
+ flag: "wx",
3657
+ mode: 384
3658
+ });
3659
+ retryTransientFilesystemOperation(() => (0, import_fs5.renameSync)(temporaryPath, ownerPath));
3660
+ } finally {
3661
+ if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { force: true });
3662
+ }
3663
+ }
3664
+ function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
3665
+ if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
3666
+ temporaryCounter += 1;
3667
+ return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
3668
+ }
3669
+ function removeLeaseTemporaryPath(temporaryPath) {
3670
+ if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { recursive: true, force: true });
3671
+ }
3672
+ function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
3673
+ for (const targetPath of backupTargets) {
3674
+ const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
3675
+ if (!(0, import_fs5.existsSync)(backupPath)) continue;
3676
+ if ((0, import_fs5.existsSync)(targetPath)) (0, import_fs5.rmSync)(targetPath, { recursive: true, force: true });
3677
+ (0, import_fs5.renameSync)(backupPath, targetPath);
3678
+ }
3679
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
3680
+ for (const entry of (0, import_fs5.readdirSync)(indexPath)) {
3681
+ if (!entry.includes(temporaryOwnerMarker)) continue;
3682
+ (0, import_fs5.rmSync)(path9.join(indexPath, entry), { recursive: true, force: true });
3683
+ }
3684
+ }
3685
+ function completeLeaseRecovery(lease) {
3686
+ for (const recovery of lease.recoveries) {
3687
+ const markerOwner = readRecoveryOwner(recovery.markerPath);
3688
+ if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
3689
+ throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
3690
+ }
3691
+ }
3692
+ for (const recovery of lease.recoveries) {
3693
+ (0, import_fs5.rmSync)(recovery.markerPath, { recursive: true, force: true });
3694
+ }
3695
+ }
3696
+
3697
+ // src/utils/background-worker.ts
3698
+ var import_node_crypto = require("crypto");
3699
+ var import_node_fs = require("fs");
3700
+ var os3 = __toESM(require("os"), 1);
3701
+ var path10 = __toESM(require("path"), 1);
3702
+ var OWNER_FILE_NAME2 = "owner.json";
3703
+ var HEARTBEAT_FILE_PREFIX = "heartbeat.";
3704
+ var RECLAIM_DIRECTORY_NAME2 = "reclaim";
3705
+ var REFRESH_REQUEST_FILE_NAME = "refresh-request.json";
3706
+ var HEARTBEAT_INTERVAL_MS = 5e3;
3707
+ var STALE_LEASE_MS = 3e4;
3708
+ var RETRY_DELAY_MS = 5e3;
3709
+ 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;
3710
+ var BackgroundWorkerStopError = class extends Error {
3711
+ constructor(watcherError, autoIndexError) {
3712
+ super("Failed to stop background worker");
3713
+ this.watcherError = watcherError;
3714
+ this.autoIndexError = autoIndexError;
3715
+ this.name = "BackgroundWorkerStopError";
3716
+ }
3717
+ watcherError;
3718
+ autoIndexError;
3719
+ };
3720
+ var workers = /* @__PURE__ */ new Map();
3721
+ var workerKeysByProject = /* @__PURE__ */ new Map();
3722
+ var workerReplacementBarriers = /* @__PURE__ */ new Map();
3723
+ function getErrorCode2(error) {
3724
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
3725
+ }
3726
+ function canonicalizePath(targetPath) {
3727
+ const resolved = path10.resolve(targetPath);
3728
+ if ((0, import_node_fs.existsSync)(resolved)) {
3729
+ try {
3730
+ return import_node_fs.realpathSync.native(resolved);
3731
+ } catch {
3732
+ return resolved;
3733
+ }
3734
+ }
3735
+ const parent = path10.dirname(resolved);
3736
+ if (parent === resolved) return resolved;
3737
+ return path10.join(canonicalizePath(parent), path10.basename(resolved));
3738
+ }
3739
+ function projectLookupKey(projectRoot, host) {
3740
+ return `${host}::${canonicalizePath(projectRoot)}`;
3741
+ }
3742
+ function resolveIdentity(projectRoot, config, host) {
3743
+ const canonicalProjectRoot = canonicalizePath(projectRoot);
3744
+ const canonicalIndexPath = canonicalizePath(resolveProjectIndexPath(projectRoot, config.scope, host));
3745
+ return {
3746
+ canonicalIndexPath,
3747
+ canonicalProjectRoot,
3748
+ key: `${canonicalIndexPath}::${canonicalProjectRoot}`
3749
+ };
3750
+ }
3751
+ function controllerKey(identity, host) {
3752
+ return `${identity.key}::${host}`;
3753
+ }
3754
+ function leaseDirectoryName(identity) {
3755
+ const hash = (0, import_node_crypto.createHash)("sha256").update(identity.key).digest("hex").slice(0, 32);
3756
+ return `background-worker.${hash}.lease`;
3757
+ }
3758
+ function leasePathFor(identity) {
3759
+ return path10.join(identity.canonicalIndexPath, leaseDirectoryName(identity));
3760
+ }
3761
+ function parseOwner2(value) {
3762
+ if (typeof value !== "object" || value === null) return null;
3763
+ const candidate = value;
3764
+ if (candidate.version !== 1) return null;
3765
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
3766
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
3767
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3768
+ if (typeof candidate.heartbeatAt !== "string" || Number.isNaN(Date.parse(candidate.heartbeatAt))) return null;
3769
+ if (typeof candidate.projectRoot !== "string" || candidate.projectRoot.length === 0) return null;
3770
+ if (typeof candidate.indexPath !== "string" || candidate.indexPath.length === 0) return null;
3771
+ if (typeof candidate.token !== "string" || !UUID_PATTERN2.test(candidate.token)) return null;
3772
+ return candidate;
3773
+ }
3774
+ function parseHeartbeat(value, expectedToken) {
3775
+ if (typeof value !== "object" || value === null) return null;
3776
+ const candidate = value;
3777
+ if (candidate.version !== 1 || candidate.token !== expectedToken) return null;
3778
+ if (typeof candidate.heartbeatAt !== "string" || Number.isNaN(Date.parse(candidate.heartbeatAt))) return null;
3779
+ return candidate;
3780
+ }
3781
+ function parseReclaimOwner2(value) {
3782
+ if (typeof value !== "object" || value === null) return null;
3783
+ const candidate = value;
3784
+ if (candidate.version !== 1) return null;
3785
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
3786
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
3787
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3788
+ if (typeof candidate.token !== "string" || !UUID_PATTERN2.test(candidate.token)) return null;
3789
+ if (candidate.expectedOwnerToken !== null && (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN2.test(candidate.expectedOwnerToken))) return null;
3790
+ return candidate;
3791
+ }
3792
+ function heartbeatPath(leasePath, token) {
3793
+ return path10.join(leasePath, `${HEARTBEAT_FILE_PREFIX}${token}.json`);
3794
+ }
3795
+ function reclaimPath(leasePath) {
3796
+ return path10.join(leasePath, RECLAIM_DIRECTORY_NAME2);
3797
+ }
3798
+ function refreshRequestPath(leasePath) {
3799
+ return path10.join(leasePath, REFRESH_REQUEST_FILE_NAME);
3800
+ }
3801
+ function readLeaseOwner(leasePath) {
3802
+ try {
3803
+ return parseOwner2(JSON.parse((0, import_node_fs.readFileSync)(path10.join(leasePath, OWNER_FILE_NAME2), "utf-8")));
3804
+ } catch {
3805
+ return null;
3806
+ }
3807
+ }
3808
+ function readOwner(leasePath) {
3809
+ const owner = readLeaseOwner(leasePath);
3810
+ if (!owner) return null;
3811
+ try {
3812
+ const heartbeat = parseHeartbeat(
3813
+ JSON.parse((0, import_node_fs.readFileSync)(heartbeatPath(leasePath, owner.token), "utf-8")),
3814
+ owner.token
3815
+ );
3816
+ return heartbeat ? { ...owner, heartbeatAt: heartbeat.heartbeatAt } : owner;
3817
+ } catch {
3818
+ return owner;
3819
+ }
3820
+ }
3821
+ function readReclaimOwner2(leasePath) {
3822
+ try {
3823
+ return parseReclaimOwner2(JSON.parse((0, import_node_fs.readFileSync)(path10.join(reclaimPath(leasePath), OWNER_FILE_NAME2), "utf-8")));
3824
+ } catch {
3825
+ return null;
3826
+ }
3827
+ }
3828
+ function ownerLiveness(owner) {
3829
+ if (owner.hostname !== os3.hostname()) return "unknown";
3830
+ try {
3831
+ process.kill(owner.pid, 0);
3832
+ return "alive";
3833
+ } catch (error) {
3834
+ const code = getErrorCode2(error);
3835
+ if (code === "ESRCH") return "dead";
3836
+ if (code === "EPERM") return "alive";
3837
+ return "unknown";
3838
+ }
3839
+ }
3840
+ function isHeartbeatExpired(owner) {
3841
+ return Date.now() - Date.parse(owner.heartbeatAt) >= STALE_LEASE_MS;
3842
+ }
3843
+ function sameOwner2(left, right) {
3844
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
3845
+ }
3846
+ function writeHeartbeat(leasePath, owner) {
3847
+ const targetPath = heartbeatPath(leasePath, owner.token);
3848
+ const temporaryPath = `${targetPath}.tmp.${process.pid}.${owner.token}.${(0, import_node_crypto.randomUUID)()}`;
3849
+ const heartbeat = {
3850
+ version: 1,
3851
+ token: owner.token,
3852
+ heartbeatAt: owner.heartbeatAt
3853
+ };
3854
+ try {
3855
+ (0, import_node_fs.writeFileSync)(temporaryPath, JSON.stringify(heartbeat), {
3856
+ encoding: "utf-8",
3857
+ flag: "wx",
3858
+ mode: 384
3859
+ });
3860
+ (0, import_node_fs.renameSync)(temporaryPath, targetPath);
3861
+ const currentOwner = readLeaseOwner(leasePath);
3862
+ return currentOwner !== null && sameOwner2(currentOwner, owner);
3863
+ } finally {
3864
+ if ((0, import_node_fs.existsSync)(temporaryPath)) (0, import_node_fs.rmSync)(temporaryPath, { force: true });
3865
+ }
3866
+ }
3867
+ function requestRefreshFromLeader(leasePath, allowDisabledAutoIndex) {
3868
+ const requestPath = refreshRequestPath(leasePath);
3869
+ const temporaryPath = `${requestPath}.tmp.${process.pid}.${(0, import_node_crypto.randomUUID)()}`;
3870
+ try {
3871
+ const request = {
3872
+ allowDisabledAutoIndex,
3873
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3874
+ version: 1
3875
+ };
3876
+ (0, import_node_fs.writeFileSync)(temporaryPath, JSON.stringify(request), {
3877
+ encoding: "utf-8",
3878
+ flag: "wx",
3879
+ mode: 384
3880
+ });
3881
+ (0, import_node_fs.renameSync)(temporaryPath, requestPath);
3882
+ } catch (error) {
3883
+ if (getErrorCode2(error) !== "ENOENT") {
3884
+ console.error("[codebase-index] Failed to request background index refresh from the project worker:", error);
3885
+ }
3886
+ } finally {
3887
+ if ((0, import_node_fs.existsSync)(temporaryPath)) (0, import_node_fs.rmSync)(temporaryPath, { force: true });
3888
+ }
3889
+ }
3890
+ function consumeRefreshRequest(leasePath) {
3891
+ const requestPath = refreshRequestPath(leasePath);
3892
+ const claimedPath = `${requestPath}.handling.${process.pid}.${(0, import_node_crypto.randomUUID)()}`;
3893
+ try {
3894
+ (0, import_node_fs.renameSync)(requestPath, claimedPath);
3895
+ } catch (error) {
3896
+ if (getErrorCode2(error) === "ENOENT") return null;
3897
+ throw error;
3898
+ }
3899
+ try {
3900
+ const value = JSON.parse((0, import_node_fs.readFileSync)(claimedPath, "utf-8"));
3901
+ return {
3902
+ allowDisabledAutoIndex: value.version === 1 && value.allowDisabledAutoIndex === true,
3903
+ requestedAt: typeof value.requestedAt === "string" ? value.requestedAt : (/* @__PURE__ */ new Date()).toISOString(),
3904
+ version: 1
3905
+ };
3906
+ } catch {
3907
+ return { allowDisabledAutoIndex: false, requestedAt: (/* @__PURE__ */ new Date()).toISOString(), version: 1 };
3908
+ } finally {
3909
+ (0, import_node_fs.rmSync)(claimedPath, { force: true });
3910
+ }
3911
+ }
3912
+ function publishLease(leasePath, owner) {
3913
+ const candidatePath = `${leasePath}.candidate.${process.pid}.${owner.token}`;
3914
+ try {
3915
+ (0, import_node_fs.mkdirSync)(candidatePath, { mode: 448 });
3916
+ } catch (error) {
3917
+ if (getErrorCode2(error) === "ENOENT") return false;
3918
+ throw error;
3919
+ }
3920
+ try {
3921
+ (0, import_node_fs.writeFileSync)(path10.join(candidatePath, OWNER_FILE_NAME2), JSON.stringify(owner), {
3922
+ encoding: "utf-8",
3923
+ flag: "wx",
3924
+ mode: 384
3925
+ });
3926
+ if ((0, import_node_fs.existsSync)(leasePath)) return false;
3927
+ try {
3928
+ (0, import_node_fs.renameSync)(candidatePath, leasePath);
3929
+ return true;
3930
+ } catch (error) {
3931
+ if ((0, import_node_fs.existsSync)(leasePath) || getErrorCode2(error) === "ENOENT") return false;
3932
+ throw error;
3933
+ }
3934
+ } finally {
3935
+ if ((0, import_node_fs.existsSync)(candidatePath)) (0, import_node_fs.rmSync)(candidatePath, { recursive: true, force: true });
3936
+ }
3937
+ }
3938
+ function sameReclaimOwner2(left, right) {
3939
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
3940
+ }
3941
+ function reclaimerLiveness(owner) {
3942
+ return ownerLiveness(owner);
3943
+ }
3944
+ function isReclaimMarkerExpired(leasePath, owner) {
3945
+ const startedAt = owner ? Date.parse(owner.startedAt) : (() => {
3946
+ try {
3947
+ return (0, import_node_fs.lstatSync)(reclaimPath(leasePath)).mtimeMs;
3948
+ } catch {
3949
+ return Date.now();
3950
+ }
3951
+ })();
3952
+ return Date.now() - startedAt >= STALE_LEASE_MS;
3953
+ }
3954
+ function hasActiveReclaimMarker(leasePath, owner) {
3955
+ const marker = readReclaimOwner2(leasePath);
3956
+ return marker !== null && marker.expectedOwnerToken === owner.token && (marker.hostname !== os3.hostname() || ownerLiveness(owner) !== "alive");
3957
+ }
3958
+ function publishReclaimMarker(leasePath, expectedOwner) {
3959
+ const markerPath = reclaimPath(leasePath);
3960
+ const owner = {
3961
+ version: 1,
3962
+ pid: process.pid,
3963
+ hostname: os3.hostname(),
3964
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
3965
+ token: (0, import_node_crypto.randomUUID)(),
3966
+ expectedOwnerToken: expectedOwner?.token ?? null
3967
+ };
3968
+ try {
3969
+ (0, import_node_fs.mkdirSync)(markerPath, { mode: 448 });
3970
+ } catch (error) {
3971
+ if (getErrorCode2(error) === "EEXIST" || getErrorCode2(error) === "ENOENT") return null;
3972
+ throw error;
3973
+ }
3974
+ try {
3975
+ (0, import_node_fs.writeFileSync)(path10.join(markerPath, OWNER_FILE_NAME2), JSON.stringify(owner), {
3976
+ encoding: "utf-8",
3977
+ flag: "wx",
3978
+ mode: 384
3979
+ });
3980
+ return owner;
3981
+ } catch (error) {
3982
+ (0, import_node_fs.rmSync)(markerPath, { recursive: true, force: true });
3983
+ throw error;
3984
+ }
3985
+ }
3986
+ function removeExpiredReclaimMarker(leasePath, expectedOwner) {
3987
+ const marker = readReclaimOwner2(leasePath);
3988
+ const markerPath = reclaimPath(leasePath);
3989
+ if (!(0, import_node_fs.existsSync)(markerPath)) return false;
3990
+ if (marker && marker.expectedOwnerToken !== (expectedOwner?.token ?? null)) return false;
3991
+ if (marker && (reclaimerLiveness(marker) === "alive" || !isReclaimMarkerExpired(leasePath, marker))) return false;
3992
+ if (!marker && !isReclaimMarkerExpired(leasePath, null)) return false;
3993
+ const staleMarkerPath = `${markerPath}.stale.${marker?.pid ?? process.pid}.${marker?.token ?? (0, import_node_crypto.randomUUID)()}.${(0, import_node_crypto.randomUUID)()}`;
3994
+ try {
3995
+ (0, import_node_fs.renameSync)(markerPath, staleMarkerPath);
3996
+ } catch (error) {
3997
+ if (getErrorCode2(error) === "ENOENT") return false;
3998
+ throw error;
3999
+ }
4000
+ try {
4001
+ let claimedMarker = null;
4002
+ try {
4003
+ claimedMarker = parseReclaimOwner2(
4004
+ JSON.parse((0, import_node_fs.readFileSync)(path10.join(staleMarkerPath, OWNER_FILE_NAME2), "utf-8"))
4005
+ );
4006
+ } catch {
4007
+ claimedMarker = null;
4008
+ }
4009
+ const markerMatches = marker ? claimedMarker !== null && sameReclaimOwner2(claimedMarker, marker) : claimedMarker === null;
4010
+ if (!markerMatches || !canReclaimLease(leasePath, expectedOwner)) {
4011
+ if (!(0, import_node_fs.existsSync)(markerPath) && (0, import_node_fs.existsSync)(staleMarkerPath)) (0, import_node_fs.renameSync)(staleMarkerPath, markerPath);
4012
+ return false;
4013
+ }
4014
+ (0, import_node_fs.rmSync)(staleMarkerPath, { recursive: true, force: true });
4015
+ return true;
4016
+ } catch (error) {
4017
+ if (getErrorCode2(error) === "ENOENT") return false;
4018
+ throw error;
4019
+ }
4020
+ }
4021
+ function canReclaimLease(leasePath, expectedOwner) {
4022
+ if (!(0, import_node_fs.existsSync)(leasePath)) return false;
4023
+ if (!expectedOwner) return false;
4024
+ const currentOwner = readOwner(leasePath);
4025
+ if (!currentOwner || !sameOwner2(currentOwner, expectedOwner)) return false;
4026
+ if (currentOwner.hostname === os3.hostname()) {
4027
+ return ownerLiveness(currentOwner) === "dead";
4028
+ }
4029
+ return isHeartbeatExpired(currentOwner);
4030
+ }
4031
+ function reclaimLease(leasePath, expectedOwner) {
4032
+ let marker = null;
4033
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4034
+ marker = publishReclaimMarker(leasePath, expectedOwner);
4035
+ if (marker) break;
4036
+ if (attempt === 0 && removeExpiredReclaimMarker(leasePath, expectedOwner)) continue;
4037
+ return false;
4038
+ }
4039
+ if (!marker) return false;
4040
+ const markerPath = reclaimPath(leasePath);
4041
+ try {
4042
+ const currentMarker = readReclaimOwner2(leasePath);
4043
+ if (!currentMarker || !sameReclaimOwner2(currentMarker, marker) || !canReclaimLease(leasePath, expectedOwner)) {
4044
+ return false;
4045
+ }
4046
+ const stalePath = `${leasePath}.stale.${process.pid}.${marker.token}`;
4047
+ (0, import_node_fs.renameSync)(leasePath, stalePath);
4048
+ const quarantinedOwner = readOwner(stalePath);
4049
+ const quarantinedMarker = readReclaimOwner2(stalePath);
4050
+ if (!quarantinedMarker || !sameReclaimOwner2(quarantinedMarker, marker) || expectedOwner !== null && (!quarantinedOwner || !sameOwner2(quarantinedOwner, expectedOwner))) {
4051
+ if (!(0, import_node_fs.existsSync)(leasePath) && (0, import_node_fs.existsSync)(stalePath)) (0, import_node_fs.renameSync)(stalePath, leasePath);
4052
+ return false;
4053
+ }
4054
+ (0, import_node_fs.rmSync)(stalePath, { recursive: true, force: true });
4055
+ return true;
4056
+ } catch (error) {
4057
+ if (getErrorCode2(error) === "ENOENT") return false;
4058
+ throw error;
4059
+ } finally {
4060
+ const currentMarker = readReclaimOwner2(leasePath);
4061
+ if (currentMarker && sameReclaimOwner2(currentMarker, marker)) {
4062
+ (0, import_node_fs.rmSync)(markerPath, { recursive: true, force: true });
4063
+ }
4064
+ }
4065
+ }
4066
+ function acquireLease(identity) {
4067
+ (0, import_node_fs.mkdirSync)(identity.canonicalIndexPath, { recursive: true, mode: 448 });
4068
+ const canonicalIndexPath = import_node_fs.realpathSync.native(identity.canonicalIndexPath);
4069
+ const leasePath = path10.join(canonicalIndexPath, leaseDirectoryName({ ...identity, canonicalIndexPath }));
4070
+ for (let attempt = 0; attempt < 4; attempt += 1) {
4071
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
4072
+ const owner = {
4073
+ version: 1,
4074
+ pid: process.pid,
4075
+ hostname: os3.hostname(),
4076
+ startedAt: timestamp,
4077
+ heartbeatAt: timestamp,
4078
+ projectRoot: identity.canonicalProjectRoot,
4079
+ indexPath: canonicalIndexPath,
4080
+ token: (0, import_node_crypto.randomUUID)()
4081
+ };
4082
+ if (publishLease(leasePath, owner)) {
4083
+ return { leasePath, owner };
4084
+ }
4085
+ const existingOwner = readOwner(leasePath);
4086
+ if (existingOwner) {
4087
+ if (canReclaimLease(leasePath, existingOwner) && reclaimLease(leasePath, existingOwner)) continue;
4088
+ return null;
4089
+ }
4090
+ return null;
4091
+ }
4092
+ return null;
4093
+ }
4094
+ function releaseLease(lease) {
4095
+ const currentOwner = readOwner(lease.leasePath);
4096
+ if (!currentOwner || !sameOwner2(currentOwner, lease.owner)) return false;
4097
+ const releasePath = `${lease.leasePath}.release.${lease.owner.pid}.${lease.owner.token}`;
4098
+ try {
4099
+ (0, import_node_fs.renameSync)(lease.leasePath, releasePath);
4100
+ } catch (error) {
4101
+ if (getErrorCode2(error) === "ENOENT") return false;
4102
+ throw error;
4103
+ }
4104
+ const claimedOwner = readOwner(releasePath);
4105
+ if (!claimedOwner || !sameOwner2(claimedOwner, lease.owner)) {
4106
+ if (!(0, import_node_fs.existsSync)(lease.leasePath) && (0, import_node_fs.existsSync)(releasePath)) {
4107
+ (0, import_node_fs.renameSync)(releasePath, lease.leasePath);
4108
+ }
4109
+ return false;
4110
+ }
4111
+ (0, import_node_fs.rmSync)(releasePath, { recursive: true, force: true });
4112
+ return true;
4113
+ }
4114
+ var BackgroundWorkerController = class {
4115
+ constructor(projectRoot, host, config, hooks, identity) {
4116
+ this.projectRoot = projectRoot;
4117
+ this.host = host;
4118
+ this.config = config;
4119
+ this.hooks = hooks;
4120
+ this.identity = identity;
4121
+ }
4122
+ projectRoot;
4123
+ host;
4124
+ config;
4125
+ hooks;
4126
+ identity;
4127
+ lease = null;
4128
+ watcher = null;
4129
+ leaderReady = Promise.resolve();
4130
+ heartbeatTimer = null;
4131
+ retryTimer = null;
4132
+ teardownRetryTimer = null;
4133
+ transition = Promise.resolve();
4134
+ stopPromise = null;
4135
+ stopped = false;
4136
+ stopping = false;
4137
+ losingLeadership = false;
4138
+ restartAfterStop = false;
4139
+ leaderWorkStopped = false;
4140
+ startingLeaderWork = false;
4141
+ stopAutoIndexOnTeardown = true;
4142
+ autoIndexStarted = false;
4143
+ reportedError = null;
4144
+ update(config, hooks, options) {
4145
+ const autoIndexWasEnabled = this.config.indexing.autoIndex;
4146
+ const shouldReplaceWatcher = this.watcher !== null && hooks.watcherFactory !== void 0 && (hooks.watcherFactory === null || hooks.replaceWatcher === true);
4147
+ this.config = config;
4148
+ this.hooks = {
4149
+ ...this.hooks,
4150
+ ...hooks,
4151
+ watcherFactory: hooks.watcherFactory === void 0 ? this.hooks.watcherFactory : hooks.watcherFactory,
4152
+ watcherFactoryForConfig: hooks.watcherFactoryForConfig === void 0 ? this.hooks.watcherFactoryForConfig : hooks.watcherFactoryForConfig
4153
+ };
4154
+ if (autoIndexWasEnabled && !config.indexing.autoIndex || options.restartAutoIndex === true && config.indexing.autoIndex && !this.startingLeaderWork) {
4155
+ this.autoIndexStarted = false;
4156
+ }
4157
+ if (!this.canRun()) {
4158
+ void this.stop().catch((error) => {
4159
+ console.error("[codebase-index] Failed to stop background worker after disabling automatic work:", error);
4160
+ });
4161
+ return;
4162
+ }
4163
+ if (shouldReplaceWatcher) {
4164
+ void this.enqueue(async () => {
4165
+ const watcher = this.watcher;
4166
+ if (watcher) {
4167
+ await watcher.stop();
4168
+ if (this.watcher === watcher) this.watcher = null;
4169
+ }
4170
+ if (this.lease && !this.stopped) this.startLeaderWork();
4171
+ }).catch((error) => {
4172
+ console.error("[codebase-index] Failed to replace background file watcher:", error);
4173
+ });
4174
+ }
4175
+ this.start();
4176
+ }
4177
+ startAfter(activation) {
4178
+ this.transition = activation.catch(() => void 0);
4179
+ this.start();
4180
+ }
4181
+ start() {
4182
+ if (!this.canRun() || this.losingLeadership) return;
4183
+ if (this.stopping) {
4184
+ this.restartAfterStop = true;
4185
+ return;
4186
+ }
4187
+ this.stopped = false;
4188
+ void this.enqueue(async () => {
4189
+ if (this.stopped || this.stopping || this.losingLeadership || !this.canRun()) return;
4190
+ if (!this.lease) {
4191
+ try {
4192
+ this.lease = acquireLease(this.identity);
4193
+ this.reportedError = null;
4194
+ } catch (error) {
4195
+ this.reportAcquireError(error);
4196
+ this.scheduleRetry();
4197
+ return;
4198
+ }
4199
+ }
4200
+ if (!this.lease) {
4201
+ this.scheduleRetry();
4202
+ return;
4203
+ }
4204
+ this.startHeartbeat();
4205
+ this.startLeaderWork();
4206
+ });
4207
+ }
4208
+ waitForStart() {
4209
+ return this.transition.catch(() => void 0).then(() => this.leaderReady);
4210
+ }
4211
+ requestRefresh(allowDisabledAutoIndex = false) {
4212
+ this.start();
4213
+ if (!this.isLeader()) {
4214
+ requestRefreshFromLeader(leasePathFor(this.identity), allowDisabledAutoIndex);
4215
+ return;
4216
+ }
4217
+ void this.enqueue(async () => {
4218
+ if (this.stopped || !this.lease) return;
4219
+ this.hooks.startAutoIndex("retrieval", allowDisabledAutoIndex);
4220
+ });
4221
+ }
4222
+ isLeader() {
4223
+ return this.lease !== null && !this.stopping && !this.losingLeadership;
4224
+ }
4225
+ isStopping() {
4226
+ return this.stopping;
4227
+ }
4228
+ getHooksForConfig(config) {
4229
+ const watcherFactoryForConfig = this.hooks.watcherFactoryForConfig;
4230
+ if (!watcherFactoryForConfig) return this.hooks;
4231
+ return {
4232
+ ...this.hooks,
4233
+ watcherFactory: watcherFactoryForConfig(config),
4234
+ replaceWatcher: true
4235
+ };
4236
+ }
4237
+ attachWatcher(watcherFactory, watcherFactoryForConfig) {
4238
+ if (this.hooks.watcherFactory !== void 0) return;
4239
+ this.hooks = {
4240
+ ...this.hooks,
4241
+ watcherFactory,
4242
+ watcherFactoryForConfig: watcherFactoryForConfig ?? this.hooks.watcherFactoryForConfig
4243
+ };
4244
+ this.start();
4245
+ }
4246
+ async stop(stopAutoIndex = true) {
4247
+ if (this.stopPromise) return this.stopPromise;
4248
+ this.stopped = true;
4249
+ this.stopping = true;
4250
+ this.stopAutoIndexOnTeardown &&= stopAutoIndex;
4251
+ this.clearRetryTimer();
4252
+ const attempt = this.enqueue(async () => {
4253
+ try {
4254
+ const lease = this.lease;
4255
+ if (this.leaderWorkStopped) {
4256
+ if (lease) {
4257
+ this.releaseStoppedLease(lease);
4258
+ } else {
4259
+ this.finishStoppedLease();
4260
+ }
4261
+ return;
4262
+ }
4263
+ const hadLeaderWork = lease !== null || this.watcher !== null || this.autoIndexStarted;
4264
+ const stopped = await this.stopLeaderWork(hadLeaderWork && this.stopAutoIndexOnTeardown);
4265
+ if (!lease) {
4266
+ this.finishStoppedLease();
4267
+ return;
4268
+ }
4269
+ if (!stopped.completed) {
4270
+ this.releaseLeaseWhenAutoIndexStops(lease, stopped.completion);
4271
+ return;
4272
+ }
4273
+ this.leaderWorkStopped = true;
4274
+ this.releaseStoppedLease(lease);
4275
+ } catch (error) {
4276
+ this.scheduleTeardownRetry();
4277
+ throw error;
4278
+ }
4279
+ });
4280
+ const completion = attempt.finally(() => {
4281
+ if (this.stopPromise === completion) this.stopPromise = null;
4282
+ });
4283
+ this.stopPromise = completion;
4284
+ return completion;
4285
+ }
4286
+ canRun() {
4287
+ return this.config.indexing.autoIndex || this.hooks.watcherFactory != null;
4288
+ }
4289
+ enqueue(operation) {
4290
+ const next = this.transition.catch(() => void 0).then(operation);
4291
+ this.transition = next;
4292
+ return next;
4293
+ }
4294
+ startLeaderWork() {
4295
+ if (this.stopped || this.stopping || this.losingLeadership) return;
4296
+ this.startingLeaderWork = true;
4297
+ try {
4298
+ if (this.config.indexing.autoIndex && !this.autoIndexStarted) {
4299
+ this.autoIndexStarted = true;
4300
+ this.hooks.startAutoIndex("startup");
4301
+ }
4302
+ if (!this.watcher && this.hooks.watcherFactory) {
4303
+ try {
4304
+ const watcher = this.hooks.watcherFactory();
4305
+ this.watcher = watcher;
4306
+ this.leaderReady = watcher.whenReady?.().catch((error) => {
4307
+ console.error("[codebase-index] Failed while waiting for background file watcher startup:", error);
4308
+ }) ?? Promise.resolve();
4309
+ } catch (error) {
4310
+ console.error("[codebase-index] Failed to start background file watcher:", error);
4311
+ this.leaderReady = Promise.resolve();
4312
+ }
4313
+ }
4314
+ } finally {
4315
+ this.startingLeaderWork = false;
4316
+ }
4317
+ }
4318
+ async stopLeaderWork(stopAutoIndex) {
4319
+ const watcher = this.watcher;
4320
+ let watcherError;
4321
+ if (watcher) {
4322
+ try {
4323
+ await watcher.stop();
4324
+ if (this.watcher === watcher) this.watcher = null;
4325
+ } catch (error) {
4326
+ watcherError = error;
4327
+ }
4328
+ }
4329
+ let autoIndexError;
4330
+ let autoIndexStop = {
4331
+ completed: true,
4332
+ completion: Promise.resolve()
4333
+ };
4334
+ if (stopAutoIndex) {
4335
+ try {
4336
+ autoIndexStop = await this.hooks.stopAutoIndex();
4337
+ this.autoIndexStarted = false;
4338
+ } catch (error) {
4339
+ autoIndexError = error;
4340
+ }
4341
+ }
4342
+ if (watcherError !== void 0 || autoIndexError !== void 0) {
4343
+ throw new BackgroundWorkerStopError(watcherError, autoIndexError);
4344
+ }
4345
+ return autoIndexStop;
4346
+ }
4347
+ releaseLeaseWhenAutoIndexStops(lease, completion) {
4348
+ void completion.then(
4349
+ () => {
4350
+ void this.enqueue(async () => {
4351
+ if (this.lease !== lease || !this.stopping) return;
4352
+ this.leaderWorkStopped = true;
4353
+ this.releaseStoppedLease(lease);
4354
+ }).catch((error) => {
4355
+ console.error("[codebase-index] Failed to release background worker lease after automatic indexing stopped:", error);
4356
+ this.scheduleTeardownRetry();
4357
+ });
4358
+ },
4359
+ (error) => {
4360
+ console.error("[codebase-index] Failed while waiting for automatic indexing to stop:", error);
4361
+ this.scheduleTeardownRetry();
4362
+ }
4363
+ );
4364
+ }
4365
+ releaseStoppedLease(lease) {
4366
+ if (this.lease !== lease) {
4367
+ this.finishStoppedLease();
4368
+ return;
4369
+ }
4370
+ releaseLease(lease);
4371
+ this.lease = null;
4372
+ this.finishStoppedLease();
4373
+ }
4374
+ finishStoppedLease() {
4375
+ this.leaderWorkStopped = false;
4376
+ this.stopAutoIndexOnTeardown = true;
4377
+ this.stopping = false;
4378
+ this.clearTimers();
4379
+ this.restartAfterTeardown();
4380
+ if (!this.stopped || this.stopping) return;
4381
+ const projectKey = projectLookupKey(this.projectRoot, this.host);
4382
+ const key = controllerKey(this.identity, this.host);
4383
+ if (workers.get(key) === this) workers.delete(key);
4384
+ if (workerKeysByProject.get(projectKey) === key) workerKeysByProject.delete(projectKey);
4385
+ }
4386
+ startHeartbeat() {
4387
+ if (this.heartbeatTimer) return;
4388
+ const heartbeat = () => {
4389
+ void this.heartbeat();
4390
+ };
4391
+ this.heartbeatTimer = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
4392
+ this.heartbeatTimer.unref?.();
4393
+ }
4394
+ async heartbeat() {
4395
+ const lease = this.lease;
4396
+ if (!lease || this.losingLeadership || this.stopped && !this.stopping) return;
4397
+ if (hasActiveReclaimMarker(lease.leasePath, lease.owner)) {
4398
+ await this.loseLeadership();
4399
+ return;
4400
+ }
4401
+ const currentOwner = readOwner(lease.leasePath);
4402
+ if (!currentOwner || !sameOwner2(currentOwner, lease.owner)) {
4403
+ await this.loseLeadership();
4404
+ return;
4405
+ }
4406
+ try {
4407
+ const nextOwner = { ...lease.owner, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() };
4408
+ if (!writeHeartbeat(lease.leasePath, nextOwner)) {
4409
+ await this.loseLeadership();
4410
+ return;
4411
+ }
4412
+ lease.owner = nextOwner;
4413
+ const refreshRequest = !this.stopping ? consumeRefreshRequest(lease.leasePath) : null;
4414
+ if (refreshRequest) {
4415
+ this.hooks.startAutoIndex("retrieval", refreshRequest.allowDisabledAutoIndex);
4416
+ }
4417
+ } catch (error) {
4418
+ const ownerAfterError = readOwner(lease.leasePath);
4419
+ if (hasActiveReclaimMarker(lease.leasePath, lease.owner) || !ownerAfterError || !sameOwner2(ownerAfterError, lease.owner)) {
4420
+ await this.loseLeadership();
4421
+ return;
4422
+ }
4423
+ console.error("[codebase-index] Failed to renew background worker lease:", error);
4424
+ }
4425
+ }
4426
+ async loseLeadership() {
4427
+ if (this.losingLeadership) return;
4428
+ this.losingLeadership = true;
4429
+ this.clearHeartbeat();
4430
+ await this.enqueue(async () => this.stopAfterLeadershipLoss());
3634
4431
  }
3635
- return true;
3636
- }
3637
- function setIndexLockClearRecoveryState(lease, clearRecovery) {
3638
- const currentOwner = readDirectoryOwner(lease.lockPath);
3639
- if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
3640
- throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
4432
+ async stopAfterLeadershipLoss() {
4433
+ const lease = this.lease;
4434
+ if (!lease) {
4435
+ this.losingLeadership = false;
4436
+ return;
4437
+ }
4438
+ try {
4439
+ const stopped = await this.stopLeaderWork(true);
4440
+ this.lease = null;
4441
+ this.losingLeadership = false;
4442
+ if (stopped.completed) {
4443
+ this.scheduleRetry();
4444
+ } else {
4445
+ void stopped.completion.then(() => this.scheduleRetry());
4446
+ }
4447
+ } catch (error) {
4448
+ console.error("[codebase-index] Failed to stop background work after losing its lease:", error);
4449
+ this.scheduleLostLeadershipTeardownRetry();
4450
+ }
4451
+ }
4452
+ scheduleRetry() {
4453
+ if (this.stopped || !this.canRun() || this.retryTimer) return;
4454
+ this.retryTimer = setTimeout(() => {
4455
+ this.retryTimer = null;
4456
+ this.start();
4457
+ }, RETRY_DELAY_MS);
4458
+ this.retryTimer.unref?.();
4459
+ }
4460
+ scheduleTeardownRetry() {
4461
+ if (!this.stopping || this.teardownRetryTimer) return;
4462
+ this.teardownRetryTimer = setTimeout(() => {
4463
+ this.teardownRetryTimer = null;
4464
+ void this.stop(this.stopAutoIndexOnTeardown).catch((error) => {
4465
+ console.error("[codebase-index] Failed to retry background worker teardown:", error);
4466
+ });
4467
+ }, RETRY_DELAY_MS);
4468
+ this.teardownRetryTimer.unref?.();
4469
+ }
4470
+ restartAfterTeardown() {
4471
+ if (!this.restartAfterStop || !this.canRun() || this.losingLeadership) return;
4472
+ this.restartAfterStop = false;
4473
+ this.stopped = false;
4474
+ this.start();
4475
+ }
4476
+ scheduleLostLeadershipTeardownRetry() {
4477
+ if (this.stopped || !this.losingLeadership || this.retryTimer) return;
4478
+ this.retryTimer = setTimeout(() => {
4479
+ this.retryTimer = null;
4480
+ void this.enqueue(async () => this.stopAfterLeadershipLoss());
4481
+ }, RETRY_DELAY_MS);
4482
+ this.retryTimer.unref?.();
4483
+ }
4484
+ clearHeartbeat() {
4485
+ if (!this.heartbeatTimer) return;
4486
+ clearInterval(this.heartbeatTimer);
4487
+ this.heartbeatTimer = null;
4488
+ }
4489
+ clearTimers() {
4490
+ this.clearHeartbeat();
4491
+ this.clearRetryTimer();
4492
+ if (this.teardownRetryTimer) {
4493
+ clearTimeout(this.teardownRetryTimer);
4494
+ this.teardownRetryTimer = null;
4495
+ }
4496
+ }
4497
+ clearRetryTimer() {
4498
+ if (!this.retryTimer) return;
4499
+ clearTimeout(this.retryTimer);
4500
+ this.retryTimer = null;
4501
+ }
4502
+ reportAcquireError(error) {
4503
+ const message = error instanceof Error ? error.message : String(error);
4504
+ if (this.reportedError === message) return;
4505
+ this.reportedError = message;
4506
+ console.error("[codebase-index] Failed to acquire background worker lease:", error);
3641
4507
  }
3642
- const nextOwner = { ...currentOwner };
3643
- if (clearRecovery === null) {
3644
- delete nextOwner.clearRecovery;
3645
- } else {
3646
- nextOwner.clearRecovery = clearRecovery;
4508
+ };
4509
+ function configureBackgroundWorker(projectRoot, host, config, hooks, options = {}) {
4510
+ const projectKey = projectLookupKey(projectRoot, host);
4511
+ const identity = resolveIdentity(projectRoot, config, host);
4512
+ const key = controllerKey(identity, host);
4513
+ const previousKey = workerKeysByProject.get(projectKey);
4514
+ if (previousKey && previousKey !== key) {
4515
+ const previous = workers.get(previousKey);
4516
+ const previousBarrier = workerReplacementBarriers.get(projectKey) ?? Promise.resolve();
4517
+ const stopPrevious = previous?.stop(options.stopPreviousAutoIndex ?? true) ?? Promise.resolve();
4518
+ const activation = Promise.all([previousBarrier, stopPrevious]).then(() => void 0);
4519
+ workerReplacementBarriers.set(projectKey, activation);
4520
+ workers.delete(previousKey);
4521
+ const worker2 = new BackgroundWorkerController(projectRoot, host, config, hooks, identity);
4522
+ worker2.startAfter(activation);
4523
+ workers.set(key, worker2);
4524
+ workerKeysByProject.set(projectKey, key);
4525
+ return;
3647
4526
  }
3648
- const ownerPath = path9.join(lease.lockPath, OWNER_FILE_NAME);
3649
- const temporaryPath = path9.join(
3650
- lease.lockPath,
3651
- `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${(0, import_crypto.randomUUID)()}`
3652
- );
3653
- try {
3654
- (0, import_fs5.writeFileSync)(temporaryPath, JSON.stringify(nextOwner), {
3655
- encoding: "utf-8",
3656
- flag: "wx",
3657
- mode: 384
3658
- });
3659
- retryTransientFilesystemOperation(() => (0, import_fs5.renameSync)(temporaryPath, ownerPath));
3660
- } finally {
3661
- if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { force: true });
4527
+ let worker = workers.get(key);
4528
+ if (!worker) {
4529
+ worker = new BackgroundWorkerController(projectRoot, host, config, hooks, identity);
4530
+ workers.set(key, worker);
4531
+ } else {
4532
+ worker.update(config, hooks, options);
3662
4533
  }
4534
+ workerKeysByProject.set(projectKey, key);
4535
+ worker.start();
3663
4536
  }
3664
- function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
3665
- if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
3666
- temporaryCounter += 1;
3667
- return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
4537
+ function updateBackgroundWorkerConfig(projectRoot, host, config) {
4538
+ const projectKey = projectLookupKey(projectRoot, host);
4539
+ const key = workerKeysByProject.get(projectKey);
4540
+ const worker = key ? workers.get(key) : void 0;
4541
+ if (!worker) return;
4542
+ configureBackgroundWorker(projectRoot, host, config, worker.getHooksForConfig(config), {
4543
+ stopPreviousAutoIndex: false,
4544
+ restartAutoIndex: true
4545
+ });
3668
4546
  }
3669
- function removeLeaseTemporaryPath(temporaryPath) {
3670
- if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { recursive: true, force: true });
4547
+ function waitForBackgroundWorkerStart(projectRoot, host) {
4548
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4549
+ return workers.get(key ?? "")?.waitForStart() ?? Promise.resolve();
3671
4550
  }
3672
- function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
3673
- for (const targetPath of backupTargets) {
3674
- const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
3675
- if (!(0, import_fs5.existsSync)(backupPath)) continue;
3676
- if ((0, import_fs5.existsSync)(targetPath)) (0, import_fs5.rmSync)(targetPath, { recursive: true, force: true });
3677
- (0, import_fs5.renameSync)(backupPath, targetPath);
3678
- }
3679
- const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
3680
- for (const entry of (0, import_fs5.readdirSync)(indexPath)) {
3681
- if (!entry.includes(temporaryOwnerMarker)) continue;
3682
- (0, import_fs5.rmSync)(path9.join(indexPath, entry), { recursive: true, force: true });
3683
- }
4551
+ function requestBackgroundWorkerRefresh(projectRoot, host, allowDisabledAutoIndex = false) {
4552
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4553
+ workers.get(key ?? "")?.requestRefresh(allowDisabledAutoIndex);
3684
4554
  }
3685
- function completeLeaseRecovery(lease) {
3686
- for (const recovery of lease.recoveries) {
3687
- const markerOwner = readRecoveryOwner(recovery.markerPath);
3688
- if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
3689
- throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
3690
- }
3691
- }
3692
- for (const recovery of lease.recoveries) {
3693
- (0, import_fs5.rmSync)(recovery.markerPath, { recursive: true, force: true });
3694
- }
4555
+ function isBackgroundWorkerManaged(projectRoot, host) {
4556
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4557
+ return key !== void 0 && workers.has(key);
4558
+ }
4559
+ function isBackgroundWorkerLeader(projectRoot, host) {
4560
+ const key = workerKeysByProject.get(projectLookupKey(projectRoot, host));
4561
+ return key !== void 0 && workers.get(key)?.isLeader() === true;
4562
+ }
4563
+ async function stopBackgroundWorker(projectRoot, host) {
4564
+ const projectKey = projectLookupKey(projectRoot, host);
4565
+ const key = workerKeysByProject.get(projectKey);
4566
+ const worker = key ? workers.get(key) : void 0;
4567
+ if (!worker) return;
4568
+ await worker.stop();
3695
4569
  }
3696
4570
 
3697
4571
  // src/utils/files.ts
3698
4572
  var import_ignore = __toESM(require_ignore(), 1);
3699
4573
  var import_fs6 = require("fs");
3700
- var path10 = __toESM(require("path"), 1);
4574
+ var path11 = __toESM(require("path"), 1);
3701
4575
  var PROJECT_MARKERS = [
3702
4576
  ".git",
3703
4577
  "package.json",
@@ -3715,7 +4589,7 @@ var PROJECT_MARKERS = [
3715
4589
  ];
3716
4590
  function hasProjectMarker(projectRoot) {
3717
4591
  for (const marker of PROJECT_MARKERS) {
3718
- if ((0, import_fs6.existsSync)(path10.join(projectRoot, marker))) {
4592
+ if ((0, import_fs6.existsSync)(path11.join(projectRoot, marker))) {
3719
4593
  return true;
3720
4594
  }
3721
4595
  }
@@ -3742,33 +4616,53 @@ function createIgnoreFilter(projectRoot) {
3742
4616
  "**/*build*/**"
3743
4617
  ];
3744
4618
  ig.add(defaultIgnores);
3745
- const gitignorePath = path10.join(projectRoot, ".gitignore");
4619
+ const gitignorePath = path11.join(projectRoot, ".gitignore");
3746
4620
  if ((0, import_fs6.existsSync)(gitignorePath)) {
3747
4621
  const gitignoreContent = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
3748
4622
  ig.add(gitignoreContent);
3749
4623
  }
3750
4624
  return ig;
3751
4625
  }
3752
- function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3753
- const relativePath = path10.relative(projectRoot, filePath);
3754
- if (hasFilteredPathSegment(relativePath, path10.sep)) {
3755
- return false;
3756
- }
3757
- if (ignoreFilter.ignores(relativePath)) {
3758
- return false;
4626
+ function toPosixRelativePath(relativePath) {
4627
+ return relativePath.split(path11.sep).join("/");
4628
+ }
4629
+ function matchesAnyGlob(filePath, patterns) {
4630
+ const normalized = toPosixRelativePath(filePath);
4631
+ return patterns.some((pattern) => matchGlob(normalized, pattern));
4632
+ }
4633
+ function isExcludedByPatterns(relativePath, excludePatterns) {
4634
+ return matchesAnyGlob(relativePath, excludePatterns);
4635
+ }
4636
+ function isExcludedDirectory(relativePath, excludePatterns) {
4637
+ const normalized = toPosixRelativePath(relativePath);
4638
+ if (matchesAnyGlob(normalized, excludePatterns)) {
4639
+ return true;
3759
4640
  }
3760
4641
  for (const pattern of excludePatterns) {
3761
- if (matchGlob(relativePath, pattern)) {
3762
- return false;
4642
+ const posixPattern = toPosixRelativePath(pattern).replace(/\/+$/, "");
4643
+ if (!posixPattern.endsWith("/**")) {
4644
+ continue;
3763
4645
  }
3764
- }
3765
- for (const pattern of includePatterns) {
3766
- if (matchGlob(relativePath, pattern)) {
4646
+ const directoryPattern = posixPattern.slice(0, -3);
4647
+ if (directoryPattern && matchesAnyGlob(normalized, [directoryPattern])) {
3767
4648
  return true;
3768
4649
  }
3769
4650
  }
3770
4651
  return false;
3771
4652
  }
4653
+ function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
4654
+ const relativePath = toPosixRelativePath(path11.relative(projectRoot, filePath));
4655
+ if (hasFilteredPathSegment(relativePath, "/")) {
4656
+ return false;
4657
+ }
4658
+ if (ignoreFilter.ignores(relativePath)) {
4659
+ return false;
4660
+ }
4661
+ if (isExcludedByPatterns(relativePath, excludePatterns)) {
4662
+ return false;
4663
+ }
4664
+ return matchesAnyGlob(relativePath, includePatterns);
4665
+ }
3772
4666
  function matchGlob(filePath, pattern) {
3773
4667
  if (pattern.startsWith("**/")) {
3774
4668
  const withoutPrefix = pattern.slice(3);
@@ -3789,8 +4683,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3789
4683
  const filesInDir = [];
3790
4684
  const subdirs = [];
3791
4685
  for (const entry of entries) {
3792
- const fullPath = path10.join(dir, entry.name);
3793
- const relativePath = path10.relative(projectRoot, fullPath);
4686
+ const fullPath = path11.join(dir, entry.name);
4687
+ const relativePath = toPosixRelativePath(path11.relative(projectRoot, fullPath));
3794
4688
  if (isHiddenPathSegment(entry.name)) {
3795
4689
  if (entry.isDirectory()) {
3796
4690
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3808,6 +4702,10 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3808
4702
  continue;
3809
4703
  }
3810
4704
  if (entry.isDirectory()) {
4705
+ if (isExcludedDirectory(relativePath, excludePatterns)) {
4706
+ skipped.push({ path: relativePath, reason: "excluded" });
4707
+ continue;
4708
+ }
3811
4709
  subdirs.push({ fullPath, relativePath });
3812
4710
  } else if (entry.isFile()) {
3813
4711
  const stat5 = await import_fs6.promises.stat(fullPath);
@@ -3815,20 +4713,11 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3815
4713
  skipped.push({ path: relativePath, reason: "too_large" });
3816
4714
  continue;
3817
4715
  }
3818
- for (const pattern of excludePatterns) {
3819
- if (matchGlob(relativePath, pattern)) {
3820
- skipped.push({ path: relativePath, reason: "excluded" });
3821
- continue;
3822
- }
3823
- }
3824
- let matched = false;
3825
- for (const pattern of includePatterns) {
3826
- if (matchGlob(relativePath, pattern)) {
3827
- matched = true;
3828
- break;
3829
- }
4716
+ if (isExcludedByPatterns(relativePath, excludePatterns)) {
4717
+ skipped.push({ path: relativePath, reason: "excluded" });
4718
+ continue;
3830
4719
  }
3831
- if (matched) {
4720
+ if (matchesAnyGlob(relativePath, includePatterns)) {
3832
4721
  filesInDir.push({ path: fullPath, size: stat5.size });
3833
4722
  }
3834
4723
  }
@@ -3839,7 +4728,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3839
4728
  yield f;
3840
4729
  }
3841
4730
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3842
- skipped.push({ path: path10.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
4731
+ skipped.push({ path: toPosixRelativePath(path11.relative(projectRoot, filesInDir[i].path)), reason: "excluded" });
3843
4732
  }
3844
4733
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3845
4734
  if (canRecurse) {
@@ -3879,8 +4768,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3879
4768
  if (additionalRoots && additionalRoots.length > 0) {
3880
4769
  const normalizedRoots = /* @__PURE__ */ new Set();
3881
4770
  for (const kbRoot of additionalRoots) {
3882
- const resolved = path10.normalize(
3883
- path10.isAbsolute(kbRoot) ? kbRoot : path10.resolve(projectRoot, kbRoot)
4771
+ const resolved = path11.normalize(
4772
+ path11.isAbsolute(kbRoot) ? kbRoot : path11.resolve(projectRoot, kbRoot)
3884
4773
  );
3885
4774
  normalizedRoots.add(resolved);
3886
4775
  }
@@ -3921,7 +4810,7 @@ function getErrorMessage(error) {
3921
4810
  return error instanceof Error ? error.message : String(error);
3922
4811
  }
3923
4812
  function runCommand(file, args, options) {
3924
- return new Promise((resolve17, reject) => {
4813
+ return new Promise((resolve18, reject) => {
3925
4814
  childProcess.execFile(
3926
4815
  file,
3927
4816
  args,
@@ -3931,7 +4820,7 @@ function runCommand(file, args, options) {
3931
4820
  reject(error);
3932
4821
  return;
3933
4822
  }
3934
- resolve17(stdout);
4823
+ resolve18(stdout);
3935
4824
  }
3936
4825
  );
3937
4826
  });
@@ -4023,8 +4912,8 @@ var AutoIndexCancelledError = class extends Error {
4023
4912
  function now() {
4024
4913
  return (/* @__PURE__ */ new Date()).toISOString();
4025
4914
  }
4026
- function canonicalizePath(targetPath) {
4027
- const resolved = path11.resolve(targetPath);
4915
+ function canonicalizePath2(targetPath) {
4916
+ const resolved = path12.resolve(targetPath);
4028
4917
  if ((0, import_fs7.existsSync)(resolved)) {
4029
4918
  try {
4030
4919
  return import_fs7.realpathSync.native(resolved);
@@ -4032,20 +4921,20 @@ function canonicalizePath(targetPath) {
4032
4921
  return resolved;
4033
4922
  }
4034
4923
  }
4035
- const parent = path11.dirname(resolved);
4924
+ const parent = path12.dirname(resolved);
4036
4925
  if (parent === resolved) return resolved;
4037
- return path11.join(canonicalizePath(parent), path11.basename(resolved));
4926
+ return path12.join(canonicalizePath2(parent), path12.basename(resolved));
4038
4927
  }
4039
4928
  function isHomeDirectory(projectRoot) {
4040
- return canonicalizePath(projectRoot) === canonicalizePath(os3.homedir());
4929
+ return canonicalizePath2(projectRoot) === canonicalizePath2(os4.homedir());
4041
4930
  }
4042
- function projectLookupKey(projectRoot, host) {
4043
- return `${host}::${canonicalizePath(projectRoot)}`;
4931
+ function projectLookupKey2(projectRoot, host) {
4932
+ return `${host}::${canonicalizePath2(projectRoot)}`;
4044
4933
  }
4045
4934
  function coordinatorKey(projectRoot, config, host) {
4046
- const canonicalProjectRoot = canonicalizePath(projectRoot);
4935
+ const canonicalProjectRoot = canonicalizePath2(projectRoot);
4047
4936
  const indexPath = resolveProjectIndexPath(projectRoot, config.scope, host);
4048
- return `${canonicalizePath(indexPath)}::${canonicalProjectRoot}`;
4937
+ return `${canonicalizePath2(indexPath)}::${canonicalProjectRoot}`;
4049
4938
  }
4050
4939
  function getProjectSafety(projectRoot, config) {
4051
4940
  if (isHomeDirectory(projectRoot)) {
@@ -4076,10 +4965,10 @@ function safeFailureMessage(error) {
4076
4965
  }
4077
4966
  function cancellableDelay(delayMs, signal) {
4078
4967
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
4079
- return new Promise((resolve17, reject) => {
4968
+ return new Promise((resolve18, reject) => {
4080
4969
  const timer = setTimeout(() => {
4081
4970
  signal.removeEventListener("abort", onAbort);
4082
- resolve17();
4971
+ resolve18();
4083
4972
  }, delayMs);
4084
4973
  timer.unref?.();
4085
4974
  const onAbort = () => {
@@ -4091,18 +4980,44 @@ function cancellableDelay(delayMs, signal) {
4091
4980
  }
4092
4981
  function withTimeout(promise, timeoutMs) {
4093
4982
  if (timeoutMs <= 0) return Promise.resolve(void 0);
4094
- return new Promise((resolve17) => {
4095
- const timer = setTimeout(() => resolve17(void 0), timeoutMs);
4983
+ return new Promise((resolve18) => {
4984
+ const timer = setTimeout(() => resolve18(void 0), timeoutMs);
4096
4985
  timer.unref?.();
4097
4986
  void promise.then((value) => {
4098
4987
  clearTimeout(timer);
4099
- resolve17(value);
4988
+ resolve18(value);
4100
4989
  }, () => {
4101
4990
  clearTimeout(timer);
4102
- resolve17(void 0);
4991
+ resolve18(void 0);
4103
4992
  });
4104
4993
  });
4105
4994
  }
4995
+ function settlesWithin(promise, timeoutMs) {
4996
+ if (timeoutMs <= 0) return Promise.resolve(false);
4997
+ return new Promise((resolve18) => {
4998
+ let settled = false;
4999
+ const timer = setTimeout(() => {
5000
+ if (settled) return;
5001
+ settled = true;
5002
+ resolve18(false);
5003
+ }, timeoutMs);
5004
+ timer.unref?.();
5005
+ void promise.then(
5006
+ () => {
5007
+ if (settled) return;
5008
+ settled = true;
5009
+ clearTimeout(timer);
5010
+ resolve18(true);
5011
+ },
5012
+ () => {
5013
+ if (settled) return;
5014
+ settled = true;
5015
+ clearTimeout(timer);
5016
+ resolve18(true);
5017
+ }
5018
+ );
5019
+ });
5020
+ }
4106
5021
  function requestPriority(request) {
4107
5022
  if (request.force) return 4;
4108
5023
  if (request.source === "manual") return 3;
@@ -4113,6 +5028,7 @@ function mergeRequests(current, next) {
4113
5028
  if (!current) return next;
4114
5029
  const preferred = requestPriority(next) > requestPriority(current) ? next : current;
4115
5030
  return {
5031
+ allowDisabledAutoIndex: current.allowDisabledAutoIndex || next.allowDisabledAutoIndex,
4116
5032
  checkFreshness: current.checkFreshness && next.checkFreshness,
4117
5033
  force: current.force || next.force,
4118
5034
  onProgress: next.onProgress ?? current.onProgress,
@@ -4174,11 +5090,11 @@ var AutoIndexCoordinator = class {
4174
5090
  progress: this.status.progress ? { ...this.status.progress } : void 0
4175
5091
  };
4176
5092
  }
4177
- start(source) {
5093
+ start(source, allowDisabledAutoIndex = false) {
4178
5094
  this.refreshSafety();
4179
- if (!this.registration.config.indexing.autoIndex || !this.registration.safeToRun) return null;
5095
+ if (!this.registration.config.indexing.autoIndex && !allowDisabledAutoIndex || !this.registration.safeToRun) return null;
4180
5096
  if (this.status.state === "failed") return this.inFlight;
4181
- return this.request({ checkFreshness: true, force: false, source });
5097
+ return this.request({ allowDisabledAutoIndex, checkFreshness: true, force: false, source });
4182
5098
  }
4183
5099
  request(request) {
4184
5100
  if (this.stopped) {
@@ -4253,13 +5169,15 @@ var AutoIndexCoordinator = class {
4253
5169
  retryAttempt: void 0
4254
5170
  });
4255
5171
  const inFlight = this.inFlight;
4256
- if (inFlight) {
4257
- if (waitForCompletion) {
4258
- await inFlight;
4259
- } else {
4260
- await withTimeout(inFlight, SHUTDOWN_WAIT_MS);
4261
- }
5172
+ const completion = inFlight ? inFlight.then(() => void 0, () => void 0) : Promise.resolve();
5173
+ if (!inFlight) {
5174
+ return { completed: true, completion };
5175
+ }
5176
+ if (waitForCompletion) {
5177
+ await completion;
5178
+ return { completed: true, completion };
4262
5179
  }
5180
+ return { completed: await settlesWithin(completion, SHUTDOWN_WAIT_MS), completion };
4263
5181
  }
4264
5182
  startRequest(request) {
4265
5183
  if (this.stopped || !this.canRun(request)) {
@@ -4448,7 +5366,7 @@ var AutoIndexCoordinator = class {
4448
5366
  if (request.source === "manual" || request.source === "watcher") {
4449
5367
  return true;
4450
5368
  }
4451
- return this.registration.safeToRun && this.registration.config.indexing.autoIndex;
5369
+ return this.registration.safeToRun && (this.registration.config.indexing.autoIndex || request.allowDisabledAutoIndex === true);
4452
5370
  }
4453
5371
  shouldDeferForBattery(request) {
4454
5372
  return this.registration.backgroundIndexingPolicy !== null && (request.source === "startup" || request.source === "watcher");
@@ -4481,17 +5399,17 @@ var AutoIndexCoordinator = class {
4481
5399
  }
4482
5400
  }
4483
5401
  waitForBatteryRetry(delayMs) {
4484
- return new Promise((resolve17) => {
5402
+ return new Promise((resolve18) => {
4485
5403
  const timer = setTimeout(() => {
4486
5404
  if (this.batteryRetryTimer === timer) {
4487
5405
  this.batteryRetryTimer = null;
4488
5406
  this.resolveBatteryRetry = null;
4489
5407
  }
4490
- resolve17();
5408
+ resolve18();
4491
5409
  }, delayMs);
4492
5410
  timer.unref?.();
4493
5411
  this.batteryRetryTimer = timer;
4494
- this.resolveBatteryRetry = resolve17;
5412
+ this.resolveBatteryRetry = resolve18;
4495
5413
  });
4496
5414
  }
4497
5415
  cancelBatteryRetry() {
@@ -4499,9 +5417,9 @@ var AutoIndexCoordinator = class {
4499
5417
  clearTimeout(this.batteryRetryTimer);
4500
5418
  this.batteryRetryTimer = null;
4501
5419
  }
4502
- const resolve17 = this.resolveBatteryRetry;
5420
+ const resolve18 = this.resolveBatteryRetry;
4503
5421
  this.resolveBatteryRetry = null;
4504
- resolve17?.();
5422
+ resolve18?.();
4505
5423
  }
4506
5424
  finishBatteryCheck(batteryCheck) {
4507
5425
  if (this.batteryCheck !== batteryCheck) return;
@@ -4514,12 +5432,25 @@ var AutoIndexCoordinator = class {
4514
5432
  }
4515
5433
  };
4516
5434
  function getCoordinator(projectRoot, host) {
4517
- const key = coordinatorKeysByProject.get(projectLookupKey(projectRoot, host));
5435
+ const key = coordinatorKeysByProject.get(projectLookupKey2(projectRoot, host));
4518
5436
  return key ? coordinators.get(key) ?? null : null;
4519
5437
  }
4520
- function configureAutoIndex(projectRoot, host, config, getIndexer) {
4521
- const projectKey = projectLookupKey(projectRoot, host);
5438
+ function synchronizeBackgroundWorker(projectRoot, host, config, safeToRun) {
5439
+ if (safeToRun) {
5440
+ updateBackgroundWorkerConfig(projectRoot, host, config);
5441
+ return;
5442
+ }
5443
+ void stopBackgroundWorker(projectRoot, host).catch((error) => {
5444
+ console.error("[codebase-index] Failed to stop background worker after project safety changed:", error);
5445
+ });
5446
+ }
5447
+ function configureAutoIndex(projectRoot, host, config, getIndexer, options = {}) {
5448
+ const projectKey = projectLookupKey2(projectRoot, host);
4522
5449
  const safety = getProjectSafety(projectRoot, config);
5450
+ const synchronizeWorker = options.synchronizeBackgroundWorker ?? true;
5451
+ if (options.preserveManagedWorker === true && isBackgroundWorkerManaged(projectRoot, host)) {
5452
+ return;
5453
+ }
4523
5454
  const registration = {
4524
5455
  backgroundIndexingPolicy: createBackgroundIndexingPolicy(
4525
5456
  config.indexing.pauseBackgroundIndexingOnBattery
@@ -4537,6 +5468,9 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
4537
5468
  const stopPrevious = previousCoordinator?.stop(true) ?? Promise.resolve();
4538
5469
  const activation = Promise.all([previousBarrier, stopPrevious]).then(() => void 0);
4539
5470
  coordinatorReplacementBarriers.set(projectKey, activation);
5471
+ if (synchronizeWorker) {
5472
+ synchronizeBackgroundWorker(projectRoot, host, config, safety.safeToRun);
5473
+ }
4540
5474
  coordinators.delete(previousKey);
4541
5475
  const coordinator2 = new AutoIndexCoordinator(registration);
4542
5476
  coordinator2.activateAfter(activation);
@@ -4552,11 +5486,17 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
4552
5486
  coordinator.update(registration);
4553
5487
  }
4554
5488
  coordinatorKeysByProject.set(projectKey, key);
5489
+ if (synchronizeWorker) {
5490
+ synchronizeBackgroundWorker(projectRoot, host, config, safety.safeToRun);
5491
+ }
4555
5492
  }
4556
- function startAutoIndex(projectRoot, host, source = "startup") {
4557
- return getCoordinator(projectRoot, host)?.start(source) ?? null;
5493
+ function startAutoIndexForBackgroundWorker(projectRoot, host, source = "startup", allowDisabledAutoIndex = false) {
5494
+ return getCoordinator(projectRoot, host)?.start(source, allowDisabledAutoIndex) ?? null;
4558
5495
  }
4559
5496
  function requestBackgroundIndex(projectRoot, host) {
5497
+ if (isBackgroundWorkerManaged(projectRoot, host) && !isBackgroundWorkerLeader(projectRoot, host)) {
5498
+ return null;
5499
+ }
4560
5500
  return getCoordinator(projectRoot, host)?.request({
4561
5501
  checkFreshness: false,
4562
5502
  force: false,
@@ -4596,15 +5536,23 @@ async function waitForAutoIndexForRetrieval(projectRoot, host) {
4596
5536
  };
4597
5537
  }
4598
5538
  try {
4599
- if (await hasReadableCurrentIndex(coordinator)) return { ready: true };
5539
+ const readiness = await getSearchReadiness(coordinator);
5540
+ if (readiness.searchable) {
5541
+ return { ready: true };
5542
+ }
5543
+ if (readiness.blocked) return unavailableSnapshotResult(readiness.reason);
4600
5544
  } catch {
4601
5545
  }
4602
- const job = coordinator.start("retrieval") ?? coordinator.currentJob();
5546
+ const job = startRetrievalRefresh(projectRoot, host, coordinator);
4603
5547
  if (job) {
4604
5548
  await withTimeout(job, coordinator.getWaitMs());
5549
+ } else if (isBackgroundWorkerManaged(projectRoot, host)) {
5550
+ await waitForPublishedSnapshot(coordinator, coordinator.getWaitMs());
4605
5551
  }
4606
5552
  try {
4607
- if (await hasReadableCurrentIndex(coordinator)) return { ready: true };
5553
+ const readiness = await getSearchReadiness(coordinator);
5554
+ if (readiness.searchable) return { ready: true };
5555
+ if (readiness.blocked) return unavailableSnapshotResult(readiness.reason);
4608
5556
  } catch {
4609
5557
  }
4610
5558
  const status = coordinator.snapshot();
@@ -4625,18 +5573,52 @@ async function waitForAutoIndexForRetrieval(projectRoot, host) {
4625
5573
  text: `Automatic indexing is ${status.state}. Retry shortly or call index_status for progress. You can also run index_codebase explicitly.`
4626
5574
  };
4627
5575
  }
4628
- async function hasReadableCurrentIndex(coordinator) {
5576
+ async function stopAutoIndexForBackgroundWorker(projectRoot, host, waitForCompletion = false) {
5577
+ const coordinator = getCoordinator(projectRoot, host);
5578
+ if (!coordinator) {
5579
+ return { completed: true, completion: Promise.resolve() };
5580
+ }
5581
+ return coordinator.stop(waitForCompletion);
5582
+ }
5583
+ async function getSearchReadiness(coordinator) {
4629
5584
  const indexer = coordinator.getIndexer();
4630
5585
  if (indexer.getIndexFreshness) {
4631
5586
  const freshness = await indexer.getIndexFreshness();
4632
- return freshness.readable && freshness.current;
5587
+ const searchable = freshness.readable && freshness.current && freshness.reason === "current";
5588
+ return {
5589
+ blocked: freshness.reason === "unreadable" || freshness.reason === "incompatible" || freshness.reason === "failed-batches" || freshness.reason === "migration-required",
5590
+ reason: freshness.reason,
5591
+ searchable
5592
+ };
5593
+ }
5594
+ const indexed = (await indexer.getStatus()).indexed;
5595
+ return { blocked: false, searchable: indexed };
5596
+ }
5597
+ function unavailableSnapshotResult(reason) {
5598
+ 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.";
5599
+ return {
5600
+ ready: false,
5601
+ text: `${detail} Run index_codebase before retrying retrieval.`
5602
+ };
5603
+ }
5604
+ function startRetrievalRefresh(projectRoot, host, coordinator) {
5605
+ if (isBackgroundWorkerManaged(projectRoot, host)) {
5606
+ requestBackgroundWorkerRefresh(projectRoot, host, true);
5607
+ return isBackgroundWorkerLeader(projectRoot, host) ? coordinator.currentJob() : null;
5608
+ }
5609
+ return coordinator.start("retrieval") ?? coordinator.currentJob();
5610
+ }
5611
+ async function waitForPublishedSnapshot(coordinator, waitMs) {
5612
+ const deadline = Date.now() + waitMs;
5613
+ while (Date.now() < deadline) {
5614
+ if ((await getSearchReadiness(coordinator)).searchable) return;
5615
+ await new Promise((resolve18) => setTimeout(resolve18, Math.min(250, deadline - Date.now())));
4633
5616
  }
4634
- return (await indexer.getStatus()).indexed;
4635
5617
  }
4636
5618
 
4637
5619
  // src/tools/config-state.ts
4638
5620
  var import_fs8 = require("fs");
4639
- var path12 = __toESM(require("path"), 1);
5621
+ var path13 = __toESM(require("path"), 1);
4640
5622
  function normalizeKnowledgeBasePaths(config, projectRoot) {
4641
5623
  const normalized = { ...config };
4642
5624
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -4663,8 +5645,8 @@ function loadEditableConfig(projectRoot, host) {
4663
5645
  }
4664
5646
  function saveConfig(projectRoot, config, host) {
4665
5647
  const configPath = getConfigPath(projectRoot, host);
4666
- const configDir = path12.dirname(configPath);
4667
- const configBaseDir = path12.dirname(configDir);
5648
+ const configDir = path13.dirname(configPath);
5649
+ const configBaseDir = path13.dirname(configDir);
4668
5650
  if (!(0, import_fs8.existsSync)(configDir)) {
4669
5651
  (0, import_fs8.mkdirSync)(configDir, { recursive: true });
4670
5652
  }
@@ -4679,7 +5661,7 @@ function saveConfig(projectRoot, config, host) {
4679
5661
 
4680
5662
  // src/indexer/index.ts
4681
5663
  var import_fs12 = require("fs");
4682
- var path19 = __toESM(require("path"), 1);
5664
+ var path20 = __toESM(require("path"), 1);
4683
5665
  var import_perf_hooks = require("perf_hooks");
4684
5666
  var import_child_process4 = require("child_process");
4685
5667
  var import_util4 = require("util");
@@ -4706,7 +5688,7 @@ function pTimeout(promise, options) {
4706
5688
  } = options;
4707
5689
  let timer;
4708
5690
  let abortHandler;
4709
- const wrappedPromise = new Promise((resolve17, reject) => {
5691
+ const wrappedPromise = new Promise((resolve18, reject) => {
4710
5692
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
4711
5693
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
4712
5694
  }
@@ -4720,7 +5702,7 @@ function pTimeout(promise, options) {
4720
5702
  };
4721
5703
  signal.addEventListener("abort", abortHandler, { once: true });
4722
5704
  }
4723
- promise.then(resolve17, reject);
5705
+ promise.then(resolve18, reject);
4724
5706
  if (milliseconds === Number.POSITIVE_INFINITY) {
4725
5707
  return;
4726
5708
  }
@@ -4728,7 +5710,7 @@ function pTimeout(promise, options) {
4728
5710
  timer = customTimers.setTimeout.call(void 0, () => {
4729
5711
  if (fallback) {
4730
5712
  try {
4731
- resolve17(fallback());
5713
+ resolve18(fallback());
4732
5714
  } catch (error) {
4733
5715
  reject(error);
4734
5716
  }
@@ -4738,7 +5720,7 @@ function pTimeout(promise, options) {
4738
5720
  promise.cancel();
4739
5721
  }
4740
5722
  if (message === false) {
4741
- resolve17();
5723
+ resolve18();
4742
5724
  } else if (message instanceof Error) {
4743
5725
  reject(message);
4744
5726
  } else {
@@ -5140,7 +6122,7 @@ var PQueue = class extends import_index.default {
5140
6122
  // Assign unique ID if not provided
5141
6123
  id: options.id ?? (this.#idAssigner++).toString()
5142
6124
  };
5143
- return new Promise((resolve17, reject) => {
6125
+ return new Promise((resolve18, reject) => {
5144
6126
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
5145
6127
  let cleanupQueueAbortHandler = () => void 0;
5146
6128
  const run = async () => {
@@ -5180,7 +6162,7 @@ var PQueue = class extends import_index.default {
5180
6162
  })]);
5181
6163
  }
5182
6164
  const result = await operation;
5183
- resolve17(result);
6165
+ resolve18(result);
5184
6166
  this.emit("completed", result);
5185
6167
  } catch (error) {
5186
6168
  reject(error);
@@ -5368,13 +6350,13 @@ var PQueue = class extends import_index.default {
5368
6350
  });
5369
6351
  }
5370
6352
  async #onEvent(event, filter) {
5371
- return new Promise((resolve17) => {
6353
+ return new Promise((resolve18) => {
5372
6354
  const listener = () => {
5373
6355
  if (filter && !filter()) {
5374
6356
  return;
5375
6357
  }
5376
6358
  this.off(event, listener);
5377
- resolve17();
6359
+ resolve18();
5378
6360
  };
5379
6361
  this.on(event, listener);
5380
6362
  });
@@ -5660,7 +6642,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5660
6642
  const finalDelay = Math.min(delayTime, remainingTime);
5661
6643
  options.signal?.throwIfAborted();
5662
6644
  if (finalDelay > 0) {
5663
- await new Promise((resolve17, reject) => {
6645
+ await new Promise((resolve18, reject) => {
5664
6646
  const onAbort = () => {
5665
6647
  clearTimeout(timeoutToken);
5666
6648
  options.signal?.removeEventListener("abort", onAbort);
@@ -5668,7 +6650,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
5668
6650
  };
5669
6651
  const timeoutToken = setTimeout(() => {
5670
6652
  options.signal?.removeEventListener("abort", onAbort);
5671
- resolve17();
6653
+ resolve18();
5672
6654
  }, finalDelay);
5673
6655
  if (options.unref) {
5674
6656
  timeoutToken.unref?.();
@@ -5730,10 +6712,10 @@ async function pRetry(input, options = {}) {
5730
6712
 
5731
6713
  // src/embeddings/detector.ts
5732
6714
  var import_fs9 = require("fs");
5733
- var path13 = __toESM(require("path"), 1);
5734
- var os4 = __toESM(require("os"), 1);
6715
+ var path14 = __toESM(require("path"), 1);
6716
+ var os5 = __toESM(require("os"), 1);
5735
6717
  function getOpenCodeAuthPath() {
5736
- return path13.join(os4.homedir(), ".local", "share", "opencode", "auth.json");
6718
+ return path14.join(os5.homedir(), ".local", "share", "opencode", "auth.json");
5737
6719
  }
5738
6720
  function loadOpenCodeAuth() {
5739
6721
  const authPath = getOpenCodeAuthPath();
@@ -6030,17 +7012,17 @@ function validateExternalUrl(urlString) {
6030
7012
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6031
7013
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
6032
7014
  }
6033
- const hostname2 = parsed.hostname.toLowerCase();
6034
- if (BLOCKED_HOSTNAMES.has(hostname2)) {
6035
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
7015
+ const hostname3 = parsed.hostname.toLowerCase();
7016
+ if (BLOCKED_HOSTNAMES.has(hostname3)) {
7017
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname3})` };
6036
7018
  }
6037
7019
  for (const pattern of BLOCKED_METADATA_IPS) {
6038
- if (pattern.test(hostname2)) {
6039
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
7020
+ if (pattern.test(hostname3)) {
7021
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname3})` };
6040
7022
  }
6041
7023
  }
6042
- if (/^169\.254\./.test(hostname2)) {
6043
- return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
7024
+ if (/^169\.254\./.test(hostname3)) {
7025
+ return { valid: false, reason: `Blocked: link-local address (${hostname3})` };
6044
7026
  }
6045
7027
  return { valid: true };
6046
7028
  }
@@ -6578,6 +7560,26 @@ function formatCostEstimate(estimate) {
6578
7560
  \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
6579
7561
  `;
6580
7562
  }
7563
+ function formatDryRunEstimate(estimate) {
7564
+ return `Dry run: parsed the file set to measure the embedding workload. No embedding requests were made and the index was not changed.
7565
+
7566
+ Files to embed: ${estimate.filesCount.toLocaleString()}
7567
+ Chunks to embed: ${estimate.chunksCount.toLocaleString()}
7568
+ Tokens to embed: ${estimate.tokensToEmbed.toLocaleString()}
7569
+
7570
+ The "Tokens to embed" value uses the local estimateTokens(text) = ceil(len/4). It
7571
+ matches the live "Tokens used" counter only for providers that report usage on the
7572
+ same basis (ollama); for providers that report a server tokenizer count (OpenAI,
7573
+ Gemini, custom) it is only an estimate.
7574
+
7575
+ For a matching provider and a project-scoped force index, the force pass clears its
7576
+ own cached embeddings, so the live counter climbs to this number. A force index on a
7577
+ shared global index can reuse cached embeddings from other projects, and an
7578
+ incremental index counts cached chunks that are not re-embedded; in both cases this
7579
+ number is an upper bound on the live counter, so a progress percent against this
7580
+ total tops out below 100%.
7581
+ `;
7582
+ }
6581
7583
  function formatBytes(bytes) {
6582
7584
  if (bytes === 0) return "0 B";
6583
7585
  const k = 1024;
@@ -7112,8 +8114,8 @@ function extractParamNames(params) {
7112
8114
  }
7113
8115
 
7114
8116
  // src/native/binding.ts
7115
- var os5 = __toESM(require("os"), 1);
7116
- var path14 = __toESM(require("path"), 1);
8117
+ var os6 = __toESM(require("os"), 1);
8118
+ var path15 = __toESM(require("path"), 1);
7117
8119
  var module2 = __toESM(require("module"), 1);
7118
8120
  var import_node_url = require("url");
7119
8121
 
@@ -7150,7 +8152,7 @@ var STABLE_NATIVE_BINARY_NAME = IDENTITY_CATALOG.native.binaryName;
7150
8152
 
7151
8153
  // src/native/binding.ts
7152
8154
  var import_meta = {};
7153
- function getNativeBindingFilename(platform2 = os5.platform(), arch2 = os5.arch()) {
8155
+ function getNativeBindingFilename(platform2 = os6.platform(), arch2 = os6.arch()) {
7154
8156
  if (platform2 === "darwin" && arch2 === "arm64") {
7155
8157
  return `${STABLE_NATIVE_BINARY_NAME}.darwin-arm64.node`;
7156
8158
  }
@@ -7168,25 +8170,25 @@ function getNativeBindingFilename(platform2 = os5.platform(), arch2 = os5.arch()
7168
8170
  }
7169
8171
  throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
7170
8172
  }
7171
- function resolveNativeBindingPath(packageRoot, platform2 = os5.platform(), arch2 = os5.arch()) {
7172
- return path14.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
8173
+ function resolveNativeBindingPath(packageRoot, platform2 = os6.platform(), arch2 = os6.arch()) {
8174
+ return path15.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
7173
8175
  }
7174
8176
  function getNativeBinding() {
7175
8177
  let currentDir;
7176
8178
  let requireTarget;
7177
8179
  if (typeof import_meta !== "undefined" && import_meta.url) {
7178
- currentDir = path14.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
8180
+ currentDir = path15.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
7179
8181
  requireTarget = import_meta.url;
7180
8182
  } else if (typeof __dirname !== "undefined") {
7181
8183
  currentDir = __dirname;
7182
8184
  requireTarget = __filename;
7183
8185
  } else {
7184
8186
  currentDir = process.cwd();
7185
- requireTarget = path14.join(currentDir, "index.js");
8187
+ requireTarget = path15.join(currentDir, "index.js");
7186
8188
  }
7187
8189
  const normalizedDir = currentDir.replace(/\\/g, "/");
7188
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path14.join("src", "native"));
7189
- const packageRoot = isDevMode ? path14.resolve(currentDir, "../..") : path14.resolve(currentDir, "..");
8190
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path15.join("src", "native"));
8191
+ const packageRoot = isDevMode ? path15.resolve(currentDir, "../..") : path15.resolve(currentDir, "..");
7190
8192
  const nativePath = resolveNativeBindingPath(packageRoot);
7191
8193
  const require2 = module2.createRequire(requireTarget);
7192
8194
  return require2(nativePath);
@@ -7770,8 +8772,8 @@ var Database = class _Database {
7770
8772
 
7771
8773
  // src/git/branch-materialization.ts
7772
8774
  var import_fs10 = require("fs");
7773
- var os6 = __toESM(require("os"), 1);
7774
- var path15 = __toESM(require("path"), 1);
8775
+ var os7 = __toESM(require("os"), 1);
8776
+ var path16 = __toESM(require("path"), 1);
7775
8777
 
7776
8778
  // src/git/branch-resolution.ts
7777
8779
  var import_child_process = require("child_process");
@@ -8090,13 +9092,13 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
8090
9092
  return false;
8091
9093
  }
8092
9094
  function isPathWithinRoot(filePath, rootPath) {
8093
- const relative14 = path15.relative(path15.resolve(rootPath), path15.resolve(filePath));
8094
- return relative14 === "" || !relative14.startsWith(`..${path15.sep}`) && relative14 !== ".." && !path15.isAbsolute(relative14);
9095
+ const relative14 = path16.relative(path16.resolve(rootPath), path16.resolve(filePath));
9096
+ return relative14 === "" || !relative14.startsWith(`..${path16.sep}`) && relative14 !== ".." && !path16.isAbsolute(relative14);
8095
9097
  }
8096
9098
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
8097
9099
  if (await pathExists(worktreePath)) return false;
8098
9100
  const commonDir = await runGit(projectRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
8099
- const registrationsRoot = path15.join(commonDir, "worktrees");
9101
+ const registrationsRoot = path16.join(commonDir, "worktrees");
8100
9102
  let entries;
8101
9103
  try {
8102
9104
  entries = await import_fs10.promises.readdir(registrationsRoot, { withFileTypes: true });
@@ -8107,16 +9109,16 @@ async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath)
8107
9109
  const target = canonicalizePathForComparison(worktreePath);
8108
9110
  for (const entry of entries) {
8109
9111
  if (!entry.isDirectory()) continue;
8110
- const registrationPath = path15.join(registrationsRoot, entry.name);
9112
+ const registrationPath = path16.join(registrationsRoot, entry.name);
8111
9113
  if (!isPathWithinRoot(registrationPath, registrationsRoot)) continue;
8112
9114
  let gitdirPath;
8113
9115
  try {
8114
- gitdirPath = (await import_fs10.promises.readFile(path15.join(registrationPath, "gitdir"), "utf8")).trim();
9116
+ gitdirPath = (await import_fs10.promises.readFile(path16.join(registrationPath, "gitdir"), "utf8")).trim();
8115
9117
  } catch {
8116
9118
  continue;
8117
9119
  }
8118
- const resolvedGitdirPath = path15.isAbsolute(gitdirPath) ? gitdirPath : path15.resolve(registrationPath, gitdirPath);
8119
- if (canonicalizePathForComparison(path15.dirname(resolvedGitdirPath)) !== target) continue;
9120
+ const resolvedGitdirPath = path16.isAbsolute(gitdirPath) ? gitdirPath : path16.resolve(registrationPath, gitdirPath);
9121
+ if (canonicalizePathForComparison(path16.dirname(resolvedGitdirPath)) !== target) continue;
8120
9122
  await import_fs10.promises.rm(registrationPath, { recursive: true, force: true });
8121
9123
  return true;
8122
9124
  }
@@ -8134,7 +9136,7 @@ async function removeWorktree(projectRoot, worktreePath) {
8134
9136
  registered = await isWorktreeRegistered(projectRoot, worktreePath);
8135
9137
  } catch (error) {
8136
9138
  errors.push(asError(error));
8137
- throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path15.dirname(worktreePath)}`);
9139
+ throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path16.dirname(worktreePath)}`);
8138
9140
  }
8139
9141
  if (registered) {
8140
9142
  try {
@@ -8150,7 +9152,7 @@ async function removeWorktree(projectRoot, worktreePath) {
8150
9152
  registered = await isWorktreeRegistered(projectRoot, worktreePath);
8151
9153
  } catch (error) {
8152
9154
  errors.push(asError(error));
8153
- throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path15.dirname(worktreePath)}`);
9155
+ throw new AggregateError(errors, `Could not verify temporary worktree deregistration; preserved ${path16.dirname(worktreePath)}`);
8154
9156
  }
8155
9157
  }
8156
9158
  if (registered && !await pathExists(worktreePath)) {
@@ -8163,13 +9165,13 @@ async function removeWorktree(projectRoot, worktreePath) {
8163
9165
  }
8164
9166
  if (registered) {
8165
9167
  errors.push(new Error(`Temporary worktree remains registered: ${worktreePath}`));
8166
- throw new AggregateError(errors, `Failed to deregister temporary worktree; preserved ${path15.dirname(worktreePath)}`);
9168
+ throw new AggregateError(errors, `Failed to deregister temporary worktree; preserved ${path16.dirname(worktreePath)}`);
8167
9169
  }
8168
9170
  try {
8169
- await import_fs10.promises.rm(path15.dirname(worktreePath), { recursive: true, force: true });
9171
+ await import_fs10.promises.rm(path16.dirname(worktreePath), { recursive: true, force: true });
8170
9172
  } catch (error) {
8171
9173
  errors.push(asError(error));
8172
- throw new AggregateError(errors, `Deregistered the temporary worktree but could not remove ${path15.dirname(worktreePath)}`);
9174
+ throw new AggregateError(errors, `Deregistered the temporary worktree but could not remove ${path16.dirname(worktreePath)}`);
8173
9175
  }
8174
9176
  }
8175
9177
  async function cleanupTemporaryWorktree(projectRoot, worktreePath, temporaryRoot) {
@@ -8205,9 +9207,9 @@ async function withMaterializedBranch(request, callback) {
8205
9207
  `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.`
8206
9208
  );
8207
9209
  }
8208
- const temporaryRoot = await import_fs10.promises.mkdtemp(path15.join(os6.tmpdir(), "codebase-index-branch-"));
8209
- const worktreePath = path15.join(temporaryRoot, "worktree");
8210
- const hooksPath = path15.join(temporaryRoot, "hooks");
9210
+ const temporaryRoot = await import_fs10.promises.mkdtemp(path16.join(os7.tmpdir(), "codebase-index-branch-"));
9211
+ const worktreePath = path16.join(temporaryRoot, "worktree");
9212
+ const hooksPath = path16.join(temporaryRoot, "hooks");
8211
9213
  await import_fs10.promises.mkdir(hooksPath);
8212
9214
  const info = {
8213
9215
  branch: request.branch,
@@ -8259,7 +9261,7 @@ async function withMaterializedBranch(request, callback) {
8259
9261
  // src/tools/changed-files.ts
8260
9262
  var import_child_process2 = require("child_process");
8261
9263
  var import_fs11 = require("fs");
8262
- var path16 = __toESM(require("path"), 1);
9264
+ var path17 = __toESM(require("path"), 1);
8263
9265
  var import_util2 = require("util");
8264
9266
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
8265
9267
  var GH_PR_VIEW_FIELDS = [
@@ -8401,7 +9403,7 @@ function getHeadRepositoryIdentity(data, host) {
8401
9403
  return `${host}/${owner.toLowerCase()}/${repository.replace(/\.git$/i, "").toLowerCase()}`;
8402
9404
  }
8403
9405
  function getLocalRepositoryIdentity(projectRoot) {
8404
- let canonicalRoot = path16.resolve(projectRoot);
9406
+ let canonicalRoot = path17.resolve(projectRoot);
8405
9407
  try {
8406
9408
  canonicalRoot = import_fs11.realpathSync.native(canonicalRoot);
8407
9409
  } catch {
@@ -8462,17 +9464,17 @@ async function getMergeBase(projectRoot, baseCommit, headCommit) {
8462
9464
  return commit;
8463
9465
  }
8464
9466
  function normalizeFiles(rawFiles, projectRoot) {
8465
- const root = path16.resolve(projectRoot);
9467
+ const root = path17.resolve(projectRoot);
8466
9468
  const seen = /* @__PURE__ */ new Set();
8467
9469
  const result = [];
8468
9470
  for (const raw of rawFiles) {
8469
9471
  if (raw.length === 0) continue;
8470
- const absolute = path16.resolve(root, raw);
8471
- const relative14 = path16.relative(root, absolute);
8472
- if (path16.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative14)) {
9472
+ const absolute = path17.resolve(root, raw);
9473
+ const relative14 = path17.relative(root, absolute);
9474
+ if (path17.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path17.sep}`) || path17.isAbsolute(relative14)) {
8473
9475
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
8474
9476
  }
8475
- const cleaned = relative14.startsWith(`.${path16.sep}`) ? relative14.slice(2) : relative14;
9477
+ const cleaned = relative14.startsWith(`.${path17.sep}`) ? relative14.slice(2) : relative14;
8476
9478
  if (!seen.has(cleaned)) {
8477
9479
  seen.add(cleaned);
8478
9480
  result.push(cleaned);
@@ -8483,7 +9485,7 @@ function normalizeFiles(rawFiles, projectRoot) {
8483
9485
 
8484
9486
  // src/indexer/git-blame.ts
8485
9487
  var import_child_process3 = require("child_process");
8486
- var path17 = __toESM(require("path"), 1);
9488
+ var path18 = __toESM(require("path"), 1);
8487
9489
  var import_util3 = require("util");
8488
9490
  var execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8489
9491
  function parseGitBlamePorcelain(output) {
@@ -8521,7 +9523,7 @@ function parseGitBlamePorcelain(output) {
8521
9523
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
8522
9524
  }
8523
9525
  async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
8524
- const relativePath = path17.relative(projectRoot, filePath);
9526
+ const relativePath = path18.relative(projectRoot, filePath);
8525
9527
  try {
8526
9528
  const { stdout } = await execFileAsync3(
8527
9529
  "git",
@@ -8853,6 +9855,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
8853
9855
  "enum_declaration",
8854
9856
  "function_definition",
8855
9857
  "class_definition",
9858
+ // Ruby module/class symbols that are declaration-bearing and navigable.
9859
+ "class",
9860
+ "module",
8856
9861
  "class_specifier",
8857
9862
  "struct_specifier",
8858
9863
  "namespace_definition",
@@ -9097,8 +10102,8 @@ function pathSegmentsForAffinityMatch(filePath) {
9097
10102
  if (segments.length === 0) {
9098
10103
  return [];
9099
10104
  }
9100
- const basename9 = segments[segments.length - 1] ?? "";
9101
- const basenameWithoutExt = basename9.replace(/\.[^/.]+$/u, "");
10105
+ const basename10 = segments[segments.length - 1] ?? "";
10106
+ const basenameWithoutExt = basename10.replace(/\.[^/.]+$/u, "");
9102
10107
  const normalizedSegments = segments.map((segment) => segment.toLowerCase());
9103
10108
  return Array.from(/* @__PURE__ */ new Set([
9104
10109
  ...normalizedSegments,
@@ -9407,8 +10412,8 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
9407
10412
 
9408
10413
  // src/indexer/failed-state-persistence.ts
9409
10414
  var fs2 = __toESM(require("fs"), 1);
9410
- var import_node_crypto = require("crypto");
9411
- var path18 = __toESM(require("path"), 1);
10415
+ var import_node_crypto2 = require("crypto");
10416
+ var path19 = __toESM(require("path"), 1);
9412
10417
  var import_node_string_decoder = require("string_decoder");
9413
10418
  var CURRENT_FAILED_BATCH_VERSION = 1;
9414
10419
  var DEFAULT_MALFORMED_LINE_ACTION = "skip";
@@ -9426,7 +10431,7 @@ function* readFailedBatchRecords(filePath, options = {}) {
9426
10431
  function createFailedBatchWriter(targetPath) {
9427
10432
  const temporaryPath = createTemporaryPath(targetPath);
9428
10433
  let finalized = false;
9429
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10434
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9430
10435
  fs2.closeSync(fs2.openSync(temporaryPath, "w"));
9431
10436
  const write = (record) => {
9432
10437
  if (finalized) {
@@ -9445,7 +10450,7 @@ function createFailedBatchWriter(targetPath) {
9445
10450
  if (lines.length === 0) {
9446
10451
  return;
9447
10452
  }
9448
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10453
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9449
10454
  fs2.appendFileSync(temporaryPath, `${lines.join("\n")}
9450
10455
  `, "utf-8");
9451
10456
  };
@@ -9453,7 +10458,7 @@ function createFailedBatchWriter(targetPath) {
9453
10458
  if (finalized) {
9454
10459
  return;
9455
10460
  }
9456
- fs2.mkdirSync(path18.dirname(targetPath), { recursive: true });
10461
+ fs2.mkdirSync(path19.dirname(targetPath), { recursive: true });
9457
10462
  fs2.renameSync(temporaryPath, targetPath);
9458
10463
  finalized = true;
9459
10464
  };
@@ -9599,10 +10604,10 @@ function stripLeadingBomAndWhitespace(value) {
9599
10604
  return result;
9600
10605
  }
9601
10606
  function createTemporaryPath(targetPath) {
9602
- const randomId = (0, import_node_crypto.createHash)("sha1").update(`${Date.now()}:${(0, import_node_crypto.randomBytes)(8).toString("hex")}`).digest("hex");
9603
- const targetDir = path18.dirname(targetPath);
9604
- const baseName = path18.basename(targetPath);
9605
- return path18.join(targetDir, `.${baseName}.${randomId}.tmp`);
10607
+ const randomId = (0, import_node_crypto2.createHash)("sha1").update(`${Date.now()}:${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`).digest("hex");
10608
+ const targetDir = path19.dirname(targetPath);
10609
+ const baseName = path19.basename(targetPath);
10610
+ return path19.join(targetDir, `.${baseName}.${randomId}.tmp`);
9606
10611
  }
9607
10612
  function handleMalformedLine(filePath, lineNumber, line, error, options) {
9608
10613
  const action = options.malformedLineAction ?? DEFAULT_MALFORMED_LINE_ACTION;
@@ -9848,9 +10853,9 @@ var SWIFT_PARSER_VERSION = "1";
9848
10853
  var METAL_PARSER_VERSION = "1";
9849
10854
  var SYMBOL_EXTRACTOR_VERSION = "1";
9850
10855
  function isPathWithinRoot2(filePath, rootPath) {
9851
- const normalizedFilePath = path19.resolve(filePath);
9852
- const normalizedRoot = path19.resolve(rootPath);
9853
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path19.sep}`);
10856
+ const normalizedFilePath = path20.resolve(filePath);
10857
+ const normalizedRoot = path20.resolve(rootPath);
10858
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path20.sep}`);
9854
10859
  }
9855
10860
  function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9856
10861
  if (combined.length === 0) {
@@ -10181,10 +11186,10 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
10181
11186
  }
10182
11187
  if (options?.directory) {
10183
11188
  const candidatePath = canonicalizePathForComparison(
10184
- path19.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path19.sep))
11189
+ path20.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path20.sep))
10185
11190
  );
10186
11191
  const directoryPath = canonicalizePathForComparison(
10187
- path19.resolve(projectRoot, options.directory.trim().replace(/\\/g, path19.sep))
11192
+ path20.resolve(projectRoot, options.directory.trim().replace(/\\/g, path20.sep))
10188
11193
  );
10189
11194
  if (!isPathWithinRoot2(candidatePath, directoryPath)) return false;
10190
11195
  }
@@ -10297,26 +11302,37 @@ var Indexer = class _Indexer {
10297
11302
  return this.indexPathOverride ?? resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
10298
11303
  }
10299
11304
  toCanonicalFilePath(filePath) {
10300
- if (!path19.isAbsolute(filePath)) {
11305
+ if (!path20.isAbsolute(filePath)) {
10301
11306
  return this.resolveStoredFilePath(filePath, this.projectRoot);
10302
11307
  }
10303
- if (path19.resolve(this.materializedProjectRoot) === path19.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
11308
+ if (path20.resolve(this.materializedProjectRoot) === path20.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
10304
11309
  return filePath;
10305
11310
  }
10306
- return path19.resolve(this.projectRoot, path19.relative(this.materializedProjectRoot, filePath));
11311
+ return path20.resolve(this.projectRoot, path20.relative(this.materializedProjectRoot, filePath));
10307
11312
  }
10308
11313
  toStoredFilePath(filePath) {
10309
11314
  const canonicalFilePath = this.toCanonicalFilePath(filePath);
10310
11315
  if (this.config.scope !== "project" || !isPathWithinRoot2(canonicalFilePath, this.projectRoot)) {
10311
11316
  return canonicalFilePath;
10312
11317
  }
10313
- return path19.relative(this.projectRoot, canonicalFilePath).split(path19.sep).join("/");
11318
+ return path20.relative(this.projectRoot, canonicalFilePath).split(path20.sep).join("/");
11319
+ }
11320
+ isStoredPathExcluded(storedPath) {
11321
+ let matchPath = storedPath.split(path20.sep).join("/");
11322
+ if (path20.isAbsolute(storedPath)) {
11323
+ const relativePath = path20.relative(this.projectRoot, storedPath).split(path20.sep).join("/");
11324
+ if (relativePath.startsWith("..") || path20.isAbsolute(relativePath)) {
11325
+ return false;
11326
+ }
11327
+ matchPath = relativePath;
11328
+ }
11329
+ return isExcludedByPatterns(matchPath, this.config.exclude);
10314
11330
  }
10315
11331
  resolveStoredFilePath(filePath, rootPath = this.projectRoot) {
10316
- if (path19.isAbsolute(filePath)) {
11332
+ if (path20.isAbsolute(filePath)) {
10317
11333
  return filePath;
10318
11334
  }
10319
- const resolvedPath = path19.resolve(rootPath, ...filePath.split("/"));
11335
+ const resolvedPath = path20.resolve(rootPath, ...filePath.split("/"));
10320
11336
  if (!isPathWithinRoot2(resolvedPath, rootPath)) {
10321
11337
  throw new Error(`Stored project path escapes project root: ${JSON.stringify(filePath)}`);
10322
11338
  }
@@ -10340,7 +11356,7 @@ var Indexer = class _Indexer {
10340
11356
  }
10341
11357
  toMaterializedFilePath(filePath) {
10342
11358
  const storedFilePath = this.toStoredFilePath(filePath);
10343
- if (path19.isAbsolute(storedFilePath)) {
11359
+ if (path20.isAbsolute(storedFilePath)) {
10344
11360
  return storedFilePath;
10345
11361
  }
10346
11362
  return this.resolveStoredFilePath(storedFilePath, this.materializedProjectRoot);
@@ -10357,10 +11373,10 @@ var Indexer = class _Indexer {
10357
11373
  }
10358
11374
  getRuntimeArtifactPath(fileName) {
10359
11375
  const namespace = this.getRuntimeArtifactNamespace();
10360
- if (!namespace) return path19.join(this.indexPath, fileName);
10361
- const extension = path19.extname(fileName);
11376
+ if (!namespace) return path20.join(this.indexPath, fileName);
11377
+ const extension = path20.extname(fileName);
10362
11378
  const baseName = fileName.slice(0, fileName.length - extension.length);
10363
- return path19.join(this.indexPath, `${baseName}.${namespace}${extension}`);
11379
+ return path20.join(this.indexPath, `${baseName}.${namespace}${extension}`);
10364
11380
  }
10365
11381
  refreshRuntimeArtifactPaths() {
10366
11382
  this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
@@ -10373,14 +11389,14 @@ var Indexer = class _Indexer {
10373
11389
  getMaterializedKnowledgeBases() {
10374
11390
  const canonicalProjectRoot = this.getCanonicalPath(this.projectRoot);
10375
11391
  return this.config.knowledgeBases.map((knowledgeBase) => {
10376
- const configuredPath = path19.isAbsolute(knowledgeBase) ? knowledgeBase : path19.resolve(this.projectRoot, knowledgeBase);
11392
+ const configuredPath = path20.isAbsolute(knowledgeBase) ? knowledgeBase : path20.resolve(this.projectRoot, knowledgeBase);
10377
11393
  const canonicalPath = this.getCanonicalPath(configuredPath);
10378
11394
  if (!isPathWithinRoot2(canonicalPath, canonicalProjectRoot)) {
10379
11395
  return canonicalPath;
10380
11396
  }
10381
- return path19.resolve(
11397
+ return path20.resolve(
10382
11398
  this.materializedProjectRoot,
10383
- path19.relative(canonicalProjectRoot, canonicalPath)
11399
+ path20.relative(canonicalProjectRoot, canonicalPath)
10384
11400
  );
10385
11401
  });
10386
11402
  }
@@ -10388,7 +11404,7 @@ var Indexer = class _Indexer {
10388
11404
  try {
10389
11405
  return canonicalizePathForComparison(targetPath);
10390
11406
  } catch {
10391
- return path19.resolve(targetPath);
11407
+ return path20.resolve(targetPath);
10392
11408
  }
10393
11409
  }
10394
11410
  getProjectIdentityHash(projectRoot) {
@@ -10514,7 +11530,7 @@ var Indexer = class _Indexer {
10514
11530
  atomicWriteSync(targetPath, data) {
10515
11531
  const lease = this.requireActiveLease();
10516
11532
  const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
10517
- (0, import_fs12.mkdirSync)(path19.dirname(targetPath), { recursive: true });
11533
+ (0, import_fs12.mkdirSync)(path20.dirname(targetPath), { recursive: true });
10518
11534
  try {
10519
11535
  (0, import_fs12.writeFileSync)(tempPath, data);
10520
11536
  (0, import_fs12.renameSync)(tempPath, targetPath);
@@ -10524,14 +11540,14 @@ var Indexer = class _Indexer {
10524
11540
  }
10525
11541
  saveInvertedIndex(invertedIndex) {
10526
11542
  this.atomicWriteSync(
10527
- path19.join(this.indexPath, "inverted-index.json"),
11543
+ path20.join(this.indexPath, "inverted-index.json"),
10528
11544
  invertedIndex.serialize()
10529
11545
  );
10530
11546
  }
10531
11547
  getScopedRoots(projectRoot = this.projectRoot) {
10532
11548
  const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
10533
11549
  for (const kbRoot of this.config.knowledgeBases) {
10534
- roots.add(this.getCanonicalPath(path19.resolve(projectRoot, kbRoot)));
11550
+ roots.add(this.getCanonicalPath(path20.resolve(projectRoot, kbRoot)));
10535
11551
  }
10536
11552
  return Array.from(roots);
10537
11553
  }
@@ -10948,7 +11964,7 @@ var Indexer = class _Indexer {
10948
11964
  return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
10949
11965
  }
10950
11966
  hasUnknownLegacyForceIndexClear(owner) {
10951
- return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && (0, import_fs12.existsSync)(path19.join(this.indexPath, "force-index-phase"));
11967
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && (0, import_fs12.existsSync)(path20.join(this.indexPath, "force-index-phase"));
10952
11968
  }
10953
11969
  async recoverFromInterruptedIndexingUnlocked(owners) {
10954
11970
  for (const owner of owners) {
@@ -11336,7 +12352,7 @@ var Indexer = class _Indexer {
11336
12352
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
11337
12353
  const task = options.queue.add(async () => {
11338
12354
  if (options.rateLimitState.backoffMs > 0) {
11339
- await new Promise((resolve17) => setTimeout(resolve17, options.rateLimitState.backoffMs));
12355
+ await new Promise((resolve18) => setTimeout(resolve18, options.rateLimitState.backoffMs));
11340
12356
  }
11341
12357
  try {
11342
12358
  const embeddingResult = await pRetry(
@@ -11755,12 +12771,12 @@ var Indexer = class _Indexer {
11755
12771
  }
11756
12772
  }
11757
12773
  captureReaderArtifactFingerprint() {
11758
- const storePath = path19.join(this.indexPath, "vectors");
12774
+ const storePath = path20.join(this.indexPath, "vectors");
11759
12775
  return {
11760
12776
  vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
11761
- keyword: this.getReaderFileFingerprint(path19.join(this.indexPath, "inverted-index.json")),
11762
- database: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db")),
11763
- databaseIdentity: this.getReaderFileFingerprint(path19.join(this.indexPath, "codebase.db"), true)
12777
+ keyword: this.getReaderFileFingerprint(path20.join(this.indexPath, "inverted-index.json")),
12778
+ database: this.getReaderFileFingerprint(path20.join(this.indexPath, "codebase.db")),
12779
+ databaseIdentity: this.getReaderFileFingerprint(path20.join(this.indexPath, "codebase.db"), true)
11764
12780
  };
11765
12781
  }
11766
12782
  refreshReaderArtifacts() {
@@ -11785,10 +12801,10 @@ var Indexer = class _Indexer {
11785
12801
  issues.set(component, this.createReadIssue(component, message));
11786
12802
  this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
11787
12803
  };
11788
- const storePath = path19.join(this.indexPath, "vectors");
12804
+ const storePath = path20.join(this.indexPath, "vectors");
11789
12805
  const vectorMetadataPath = `${storePath}.meta.json`;
11790
- const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11791
- const dbPath = path19.join(this.indexPath, "codebase.db");
12806
+ const invertedIndexPath = path20.join(this.indexPath, "inverted-index.json");
12807
+ const dbPath = path20.join(this.indexPath, "codebase.db");
11792
12808
  if (vectorsChanged || retryDue("vectors")) {
11793
12809
  const vectorStoreExists = (0, import_fs12.existsSync)(storePath);
11794
12810
  const vectorMetadataExists = (0, import_fs12.existsSync)(vectorMetadataPath);
@@ -11904,10 +12920,10 @@ var Indexer = class _Indexer {
11904
12920
  });
11905
12921
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
11906
12922
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
11907
- const storePath = path19.join(this.indexPath, "vectors");
12923
+ const storePath = path20.join(this.indexPath, "vectors");
11908
12924
  const vectorMetadataPath = `${storePath}.meta.json`;
11909
- const invertedIndexPath = path19.join(this.indexPath, "inverted-index.json");
11910
- const dbPath = path19.join(this.indexPath, "codebase.db");
12925
+ const invertedIndexPath = path20.join(this.indexPath, "inverted-index.json");
12926
+ const dbPath = path20.join(this.indexPath, "codebase.db");
11911
12927
  let dbIsNew = !(0, import_fs12.existsSync)(dbPath);
11912
12928
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
11913
12929
  if (mode === "writer") {
@@ -12085,7 +13101,7 @@ var Indexer = class _Indexer {
12085
13101
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
12086
13102
  return {
12087
13103
  resetCorruptedIndex: true,
12088
- warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
13104
+ warning: this.getCorruptedIndexWarning(path20.join(this.indexPath, "codebase.db"))
12089
13105
  };
12090
13106
  }
12091
13107
  throw error;
@@ -12100,7 +13116,7 @@ var Indexer = class _Indexer {
12100
13116
  return;
12101
13117
  }
12102
13118
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
12103
- const storeBasePath = path19.join(this.indexPath, "vectors");
13119
+ const storeBasePath = path20.join(this.indexPath, "vectors");
12104
13120
  const storeIndexPath = storeBasePath;
12105
13121
  const storeMetadataPath = `${storeBasePath}.meta.json`;
12106
13122
  const lease = this.requireActiveLease();
@@ -12187,7 +13203,7 @@ var Indexer = class _Indexer {
12187
13203
  const names = await import_fs12.promises.readdir(this.indexPath);
12188
13204
  const runtimeStatePattern = /^(?:file-hashes|failed-batches)(?:\.[a-f0-9]{16})?\.json$/;
12189
13205
  await Promise.all(
12190
- names.filter((name) => runtimeStatePattern.test(name)).map((name) => import_fs12.promises.rm(path19.join(this.indexPath, name), { force: true }))
13206
+ names.filter((name) => runtimeStatePattern.test(name)).map((name) => import_fs12.promises.rm(path20.join(this.indexPath, name), { force: true }))
12191
13207
  );
12192
13208
  }
12193
13209
  async resetLocalIndexArtifacts() {
@@ -12203,13 +13219,13 @@ var Indexer = class _Indexer {
12203
13219
  this.readerArtifactRetryAfter.clear();
12204
13220
  this.fileHashCache.clear();
12205
13221
  const resetPaths = [
12206
- path19.join(this.indexPath, "codebase.db"),
12207
- path19.join(this.indexPath, "codebase.db-shm"),
12208
- path19.join(this.indexPath, "codebase.db-wal"),
12209
- path19.join(this.indexPath, "vectors"),
12210
- path19.join(this.indexPath, "vectors.usearch"),
12211
- path19.join(this.indexPath, "vectors.meta.json"),
12212
- path19.join(this.indexPath, "inverted-index.json")
13222
+ path20.join(this.indexPath, "codebase.db"),
13223
+ path20.join(this.indexPath, "codebase.db-shm"),
13224
+ path20.join(this.indexPath, "codebase.db-wal"),
13225
+ path20.join(this.indexPath, "vectors"),
13226
+ path20.join(this.indexPath, "vectors.usearch"),
13227
+ path20.join(this.indexPath, "vectors.meta.json"),
13228
+ path20.join(this.indexPath, "inverted-index.json")
12213
13229
  ];
12214
13230
  await Promise.all(resetPaths.map((targetPath) => import_fs12.promises.rm(targetPath, { recursive: true, force: true })));
12215
13231
  await this.removeProjectRuntimeStateArtifacts();
@@ -12219,7 +13235,7 @@ var Indexer = class _Indexer {
12219
13235
  if (!isSqliteCorruptionError(error)) {
12220
13236
  return false;
12221
13237
  }
12222
- const dbPath = path19.join(this.indexPath, "codebase.db");
13238
+ const dbPath = path20.join(this.indexPath, "codebase.db");
12223
13239
  const warning = this.getCorruptedIndexWarning(dbPath);
12224
13240
  const errorMessage = getErrorMessage4(error);
12225
13241
  if (this.config.scope === "global") {
@@ -12453,6 +13469,70 @@ var Indexer = class _Indexer {
12453
13469
  );
12454
13470
  return createCostEstimate(files, configuredProviderInfo);
12455
13471
  }
13472
+ // Dry-run counterpart to index()/forceIndex(): parse the real file set and sum
13473
+ // estimateTokens over the embedding text of every indexable chunk, without
13474
+ // calling the embedding provider or writing to the index. Read-only and
13475
+ // lock-free (mirrors estimateCost). The token sum is the exact value "Tokens
13476
+ // used" climbs to for a force index (cache bypassed); for an incremental it is
13477
+ // an upper bound because cached chunks are counted here but not re-embedded.
13478
+ // Used by index_codebase(dryRun:true) to give a stable, monotonic progress
13479
+ // denominator that matches the live "Tokens used" basis.
13480
+ async dryRunCost() {
13481
+ const { configuredProviderInfo } = await this.ensureInitialized();
13482
+ const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
13483
+ const includePatterns = [...this.config.include, ...this.config.additionalInclude];
13484
+ const { files } = await collectFiles(
13485
+ this.materializedProjectRoot,
13486
+ includePatterns,
13487
+ this.config.exclude,
13488
+ this.config.indexing.maxFileSize,
13489
+ this.getMaterializedKnowledgeBases(),
13490
+ { maxDepth: this.config.indexing.maxDepth, maxFilesPerDirectory: this.config.indexing.maxFilesPerDirectory }
13491
+ );
13492
+ let filesCount = 0;
13493
+ let chunksCount = 0;
13494
+ let tokensToEmbed = 0;
13495
+ for (const batch of iterateOrderedFileBatches(files, (f) => f.size, this.fileBatchLimits)) {
13496
+ const loadedFiles = await Promise.all(batch.map(async (f) => {
13497
+ try {
13498
+ return {
13499
+ path: this.toStoredFilePath(f.path),
13500
+ content: await import_fs12.promises.readFile(f.path, "utf-8")
13501
+ };
13502
+ } catch {
13503
+ return null;
13504
+ }
13505
+ }));
13506
+ const readable = loadedFiles.filter(
13507
+ (f) => f !== null
13508
+ );
13509
+ filesCount += readable.length;
13510
+ const contentByPath = new Map(readable.map((f) => [f.path, f.content]));
13511
+ const parsedFiles = parseFiles(readable, this.config.indexing.linesPerChunk);
13512
+ for (const parsed of parsedFiles) {
13513
+ let chunksToProcess = parsed.chunks;
13514
+ if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
13515
+ const content = contentByPath.get(parsed.path);
13516
+ if (content !== void 0) {
13517
+ chunksToProcess = parseFileAsText(parsed.path, content, this.config.indexing.linesPerChunk);
13518
+ }
13519
+ }
13520
+ chunksToProcess = selectIndexableChunks(
13521
+ chunksToProcess,
13522
+ this.config.indexing.maxChunksPerFile,
13523
+ this.config.indexing.semanticOnly
13524
+ );
13525
+ for (const chunk of chunksToProcess) {
13526
+ const texts = createEmbeddingTexts(chunk, parsed.path, maxChunkTokens);
13527
+ chunksCount += 1;
13528
+ for (const text of texts) {
13529
+ tokensToEmbed += estimateTokens(text);
13530
+ }
13531
+ }
13532
+ }
13533
+ }
13534
+ return { filesCount, chunksCount, tokensToEmbed };
13535
+ }
12456
13536
  async index(onProgress) {
12457
13537
  return this.withIndexMutationLease("index", async (recoveredOwners) => {
12458
13538
  return this.indexUnlocked(onProgress, recoveredOwners);
@@ -12542,10 +13622,10 @@ var Indexer = class _Indexer {
12542
13622
  const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
12543
13623
  const symbolExtractorMetadataKey = this.getSymbolExtractorVersionMetadataKey();
12544
13624
  const refreshCachedSymbols = database.getMetadata(symbolExtractorMetadataKey) !== SYMBOL_EXTRACTOR_VERSION;
12545
- if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".swift")) {
13625
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path20.extname(filePath).toLowerCase() === ".swift")) {
12546
13626
  this.logger.info("Reindexing cached Swift files for parser support");
12547
13627
  }
12548
- if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path19.extname(filePath).toLowerCase() === ".metal")) {
13628
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some((filePath) => path20.extname(filePath).toLowerCase() === ".metal")) {
12549
13629
  this.logger.info("Reindexing cached Metal files for parser support");
12550
13630
  }
12551
13631
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
@@ -12589,8 +13669,8 @@ var Indexer = class _Indexer {
12589
13669
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
12590
13670
  (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
12591
13671
  );
12592
- const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12593
- const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
13672
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path20.extname(storedPath).toLowerCase() === ".swift";
13673
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path20.extname(storedPath).toLowerCase() === ".metal";
12594
13674
  const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
12595
13675
  if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12596
13676
  unchangedFilePaths.add(storedPath);
@@ -12649,7 +13729,7 @@ var Indexer = class _Indexer {
12649
13729
  }
12650
13730
  }
12651
13731
  }
12652
- const shouldRetryFailedPath = (filePath) => filePath !== null && currentFileHashes.has(filePath) && unchangedFilePaths.has(filePath);
13732
+ const shouldRetryFailedPath = (filePath) => filePath !== null && !this.isStoredPathExcluded(filePath) && currentFileHashes.has(filePath) && unchangedFilePaths.has(filePath);
12653
13733
  const failedProcessing = this.prepareFailedBatchProcessing(scopedRoots, shouldRetryFailedPath);
12654
13734
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
12655
13735
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
@@ -12753,7 +13833,7 @@ var Indexer = class _Indexer {
12753
13833
  throw new Error(`Parsed file was not present in its source batch: ${parsed.path}`);
12754
13834
  }
12755
13835
  if (parsed.chunks.length === 0) {
12756
- stats.parseFailures.push(path19.isAbsolute(parsed.path) ? path19.relative(this.projectRoot, parsed.path) : parsed.path);
13836
+ stats.parseFailures.push(path20.isAbsolute(parsed.path) ? path20.relative(this.projectRoot, parsed.path) : parsed.path);
12757
13837
  }
12758
13838
  let chunksToProcess = parsed.chunks;
12759
13839
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
@@ -13088,7 +14168,7 @@ var Indexer = class _Indexer {
13088
14168
  previousBranchSymbolIds,
13089
14169
  Array.from(allSymbolIds)
13090
14170
  );
13091
- const vectorPath = path19.join(this.indexPath, "vectors");
14171
+ const vectorPath = path20.join(this.indexPath, "vectors");
13092
14172
  const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs12.existsSync)(vectorPath) && (0, import_fs12.existsSync)(`${vectorPath}.meta.json`);
13093
14173
  if (backfilledBlameMetadata || shouldFingerprintLegacyPair || removedStoredChunks) {
13094
14174
  store.save();
@@ -13947,7 +15027,7 @@ var Indexer = class _Indexer {
13947
15027
  gcOrphanSymbols: 0,
13948
15028
  gcOrphanCallEdges: 0,
13949
15029
  resetCorruptedIndex: true,
13950
- warning: this.getCorruptedIndexWarning(path19.join(this.indexPath, "codebase.db"))
15030
+ warning: this.getCorruptedIndexWarning(path20.join(this.indexPath, "codebase.db"))
13951
15031
  };
13952
15032
  }
13953
15033
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -13977,7 +15057,8 @@ var Indexer = class _Indexer {
13977
15057
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
13978
15058
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
13979
15059
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
13980
- const failedProcessing = this.prepareFailedBatchProcessing(roots, () => true);
15060
+ const shouldProcessFailedPath = (filePath) => filePath === null || !this.isStoredPathExcluded(filePath);
15061
+ const failedProcessing = this.prepareFailedBatchProcessing(roots, shouldProcessFailedPath);
13981
15062
  if (failedProcessing.latestById.size === 0) {
13982
15063
  this.finalizeFailedBatchWriteState(failedProcessing.state);
13983
15064
  return { succeeded: 0, failed: 0, remaining: 0 };
@@ -13990,7 +15071,7 @@ var Indexer = class _Indexer {
13990
15071
  const retryableChunks = this.iterateLatestFailedChunks(
13991
15072
  failedProcessing.latestById,
13992
15073
  roots,
13993
- () => true,
15074
+ shouldProcessFailedPath,
13994
15075
  maxChunkTokens
13995
15076
  );
13996
15077
  for (const retryBatch of iterateOrderedFileBatches(
@@ -14260,9 +15341,9 @@ var Indexer = class _Indexer {
14260
15341
  this.requireReadableComponents(readIssues, "database");
14261
15342
  let shortest = [];
14262
15343
  for (const branchKey of this.getBranchCatalogKeys()) {
14263
- const path30 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
14264
- if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14265
- shortest = path30;
15344
+ const path31 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
15345
+ if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
15346
+ shortest = path31;
14266
15347
  }
14267
15348
  }
14268
15349
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14310,13 +15391,13 @@ var Indexer = class _Indexer {
14310
15391
  }
14311
15392
  }
14312
15393
  if (!found) continue;
14313
- const path30 = [];
15394
+ const path31 = [];
14314
15395
  let currentSymbolId = toSymbolId;
14315
15396
  while (true) {
14316
15397
  const symbol = symbolsById.get(currentSymbolId);
14317
15398
  if (!symbol) break;
14318
15399
  const parent = parentBySymbolId.get(currentSymbolId);
14319
- path30.push({
15400
+ path31.push({
14320
15401
  symbolId: symbol.id,
14321
15402
  symbolName: symbol.name,
14322
15403
  filePath: symbol.filePath,
@@ -14326,9 +15407,9 @@ var Indexer = class _Indexer {
14326
15407
  if (!parent) break;
14327
15408
  currentSymbolId = parent.parentId;
14328
15409
  }
14329
- path30.reverse();
14330
- if (path30.length > 0 && (shortest.length === 0 || path30.length < shortest.length)) {
14331
- shortest = path30;
15410
+ path31.reverse();
15411
+ if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
15412
+ shortest = path31;
14332
15413
  }
14333
15414
  }
14334
15415
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -14479,7 +15560,7 @@ var Indexer = class _Indexer {
14479
15560
  );
14480
15561
  }
14481
15562
  }
14482
- const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path19.resolve(this.projectRoot, filePath)));
15563
+ const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path20.resolve(this.projectRoot, filePath)));
14483
15564
  const storedChangedFiles = toStoredChangedFiles(changedFiles);
14484
15565
  const directSymbols = database.getSymbolsForFiles(storedChangedFiles, branchKey);
14485
15566
  const directIds = directSymbols.map((s) => s.id);
@@ -14628,12 +15709,12 @@ var Indexer = class _Indexer {
14628
15709
  if (meta.filePath) filePaths.add(meta.filePath);
14629
15710
  }
14630
15711
  const directory = options?.directory?.replace(/\/$/, "");
14631
- const absoluteDirectoryFilter = directory ? path19.resolve(this.projectRoot, directory) : void 0;
15712
+ const absoluteDirectoryFilter = directory ? path20.resolve(this.projectRoot, directory) : void 0;
14632
15713
  for (const filePath of filePaths) {
14633
15714
  if (directory) {
14634
15715
  const absoluteFilePath = this.resolveStoredFilePath(filePath);
14635
15716
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
14636
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path19.sep));
15717
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path20.sep));
14637
15718
  if (!matchesRelative && !matchesProjectRelative) {
14638
15719
  continue;
14639
15720
  }
@@ -14750,15 +15831,24 @@ function getOrCreateIndexer(projectRoot, host) {
14750
15831
  }
14751
15832
  const indexer = new Indexer(projectRoot, config, host);
14752
15833
  indexerCache.set(key, indexer);
14753
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15834
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15835
+ preserveManagedWorker: true,
15836
+ synchronizeBackgroundWorker: false
15837
+ });
14754
15838
  return indexer;
14755
15839
  }
14756
- function initializeTools(projectRoot, config, host) {
15840
+ function initializeTools(projectRoot, config, host, options = {}) {
14757
15841
  defaultProjectRoots.set(host, projectRoot);
14758
15842
  const key = getIndexerCacheKey(projectRoot, host);
15843
+ if (options.preserveManagedWorker === true && isBackgroundWorkerManaged(projectRoot, host) && indexerCache.has(key)) {
15844
+ return;
15845
+ }
14759
15846
  configCache.set(key, config);
14760
15847
  indexerCache.set(key, new Indexer(projectRoot, config, host));
14761
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15848
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15849
+ preserveManagedWorker: options.preserveManagedWorker,
15850
+ synchronizeBackgroundWorker: false
15851
+ });
14762
15852
  }
14763
15853
  function getIndexerForProject(projectRoot, host) {
14764
15854
  const root = getProjectRoot(projectRoot, host);
@@ -14772,7 +15862,9 @@ function refreshIndexerForDirectory(projectRoot, host, config = parseConfig(load
14772
15862
  const key = getIndexerCacheKey(projectRoot, host);
14773
15863
  configCache.set(key, config);
14774
15864
  indexerCache.set(key, new Indexer(projectRoot, config, host));
14775
- configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
15865
+ configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host), {
15866
+ synchronizeBackgroundWorker: true
15867
+ });
14776
15868
  return config;
14777
15869
  }
14778
15870
  var AutoIndexRetrievalUnavailableError = class extends Error {
@@ -14799,7 +15891,7 @@ function trimOrUndefined(value) {
14799
15891
  return normalized || void 0;
14800
15892
  }
14801
15893
  function normalizeCallGraphPath(value) {
14802
- let normalized = path20.posix.normalize(value.trim().replaceAll("\\", "/"));
15894
+ let normalized = path21.posix.normalize(value.trim().replaceAll("\\", "/"));
14803
15895
  if (normalized.startsWith("./")) {
14804
15896
  normalized = normalized.slice(2);
14805
15897
  }
@@ -14992,12 +16084,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14992
16084
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14993
16085
  return { from: fromResolution, to: toResolution, path: [] };
14994
16086
  }
14995
- const path30 = await indexer.findCallPathBySymbolIds(
16087
+ const path31 = await indexer.findCallPathBySymbolIds(
14996
16088
  fromResolution.symbolId,
14997
16089
  toResolution.symbolId,
14998
16090
  maxDepth
14999
16091
  );
15000
- return { from: fromResolution, to: toResolution, path: path30 };
16092
+ return { from: fromResolution, to: toResolution, path: path31 };
15001
16093
  }
15002
16094
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
15003
16095
  const root = getProjectRoot(projectRoot, host);
@@ -15006,6 +16098,9 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
15006
16098
  if (args.estimateOnly) {
15007
16099
  return { kind: "estimate", estimate: await indexer.estimateCost() };
15008
16100
  }
16101
+ if (args.dryRun) {
16102
+ return { kind: "dryrun", dryrun: await indexer.dryRunCost() };
16103
+ }
15009
16104
  const coordinated = runCoordinatedIndex(root, host, args.force ?? false, (progress) => {
15010
16105
  if (onProgress) {
15011
16106
  void onProgress(formatProgressTitle(progress), {
@@ -15180,8 +16275,8 @@ async function getIndexLogs(projectRoot, host, args) {
15180
16275
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15181
16276
  const root = getProjectRoot(projectRoot, host);
15182
16277
  const inputPath = knowledgeBasePath.trim();
15183
- const normalizedPath2 = path20.resolve(
15184
- path20.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
16278
+ const normalizedPath2 = path21.resolve(
16279
+ path21.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
15185
16280
  );
15186
16281
  if (!(0, import_fs13.existsSync)(normalizedPath2)) {
15187
16282
  return `Error: Directory does not exist: ${normalizedPath2}`;
@@ -15217,7 +16312,7 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15217
16312
  }
15218
16313
  }
15219
16314
  for (const dotDir of sensitiveDotDirs) {
15220
- const sensitiveDir = path20.join(homeDir, dotDir);
16315
+ const sensitiveDir = path21.join(homeDir, dotDir);
15221
16316
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
15222
16317
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
15223
16318
  }
@@ -15280,7 +16375,7 @@ function listKnowledgeBases(projectRoot, host) {
15280
16375
  }
15281
16376
  result += "\n";
15282
16377
  }
15283
- const hasHostConfig = (0, import_fs13.existsSync)(path20.join(root, getHostProjectConfigRelativePath(host)));
16378
+ const hasHostConfig = (0, import_fs13.existsSync)(path21.join(root, getHostProjectConfigRelativePath(host)));
15284
16379
  if (hasHostConfig) {
15285
16380
  result += `
15286
16381
  Config sources: 1 file(s).`;
@@ -15318,7 +16413,7 @@ var import_fs14 = require("fs");
15318
16413
 
15319
16414
  // node_modules/chokidar/index.js
15320
16415
  var import_node_events = require("events");
15321
- var import_node_fs2 = require("fs");
16416
+ var import_node_fs3 = require("fs");
15322
16417
  var import_promises3 = require("fs/promises");
15323
16418
  var sp2 = __toESM(require("path"), 1);
15324
16419
 
@@ -15406,7 +16501,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
15406
16501
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
15407
16502
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
15408
16503
  if (wantBigintFsStats) {
15409
- this._stat = (path30) => statMethod(path30, { bigint: true });
16504
+ this._stat = (path31) => statMethod(path31, { bigint: true });
15410
16505
  } else {
15411
16506
  this._stat = statMethod;
15412
16507
  }
@@ -15431,8 +16526,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
15431
16526
  const par = this.parent;
15432
16527
  const fil = par && par.files;
15433
16528
  if (fil && fil.length > 0) {
15434
- const { path: path30, depth } = par;
15435
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path30));
16529
+ const { path: path31, depth } = par;
16530
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
15436
16531
  const awaited = await Promise.all(slice);
15437
16532
  for (const entry of awaited) {
15438
16533
  if (!entry)
@@ -15472,21 +16567,21 @@ var ReaddirpStream = class extends import_node_stream.Readable {
15472
16567
  this.reading = false;
15473
16568
  }
15474
16569
  }
15475
- async _exploreDir(path30, depth) {
16570
+ async _exploreDir(path31, depth) {
15476
16571
  let files;
15477
16572
  try {
15478
- files = await (0, import_promises.readdir)(path30, this._rdOptions);
16573
+ files = await (0, import_promises.readdir)(path31, this._rdOptions);
15479
16574
  } catch (error) {
15480
16575
  this._onError(error);
15481
16576
  }
15482
- return { files, depth, path: path30 };
16577
+ return { files, depth, path: path31 };
15483
16578
  }
15484
- async _formatEntry(dirent, path30) {
16579
+ async _formatEntry(dirent, path31) {
15485
16580
  let entry;
15486
- const basename9 = this._isDirent ? dirent.name : dirent;
16581
+ const basename10 = this._isDirent ? dirent.name : dirent;
15487
16582
  try {
15488
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path30, basename9));
15489
- entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename9 };
16583
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path31, basename10));
16584
+ entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename10 };
15490
16585
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
15491
16586
  } catch (err) {
15492
16587
  this._onError(err);
@@ -15556,7 +16651,7 @@ function readdirp(root, options = {}) {
15556
16651
  }
15557
16652
 
15558
16653
  // node_modules/chokidar/handler.js
15559
- var import_node_fs = require("fs");
16654
+ var import_node_fs2 = require("fs");
15560
16655
  var import_promises2 = require("fs/promises");
15561
16656
  var import_node_os = require("os");
15562
16657
  var sp = __toESM(require("path"), 1);
@@ -15885,16 +16980,16 @@ var delFromSet = (main, prop, item) => {
15885
16980
  };
15886
16981
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
15887
16982
  var FsWatchInstances = /* @__PURE__ */ new Map();
15888
- function createFsWatchInstance(path30, options, listener, errHandler, emitRaw) {
16983
+ function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
15889
16984
  const handleEvent = (rawEvent, evPath) => {
15890
- listener(path30);
15891
- emitRaw(rawEvent, evPath, { watchedPath: path30 });
15892
- if (evPath && path30 !== evPath) {
15893
- fsWatchBroadcast(sp.resolve(path30, evPath), KEY_LISTENERS, sp.join(path30, evPath));
16985
+ listener(path31);
16986
+ emitRaw(rawEvent, evPath, { watchedPath: path31 });
16987
+ if (evPath && path31 !== evPath) {
16988
+ fsWatchBroadcast(sp.resolve(path31, evPath), KEY_LISTENERS, sp.join(path31, evPath));
15894
16989
  }
15895
16990
  };
15896
16991
  try {
15897
- return (0, import_node_fs.watch)(path30, {
16992
+ return (0, import_node_fs2.watch)(path31, {
15898
16993
  persistent: options.persistent
15899
16994
  }, handleEvent);
15900
16995
  } catch (error) {
@@ -15910,12 +17005,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
15910
17005
  listener(val1, val2, val3);
15911
17006
  });
15912
17007
  };
15913
- var setFsWatchListener = (path30, fullPath, options, handlers) => {
17008
+ var setFsWatchListener = (path31, fullPath, options, handlers) => {
15914
17009
  const { listener, errHandler, rawEmitter } = handlers;
15915
17010
  let cont = FsWatchInstances.get(fullPath);
15916
17011
  let watcher;
15917
17012
  if (!options.persistent) {
15918
- watcher = createFsWatchInstance(path30, options, listener, errHandler, rawEmitter);
17013
+ watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
15919
17014
  if (!watcher)
15920
17015
  return;
15921
17016
  return watcher.close.bind(watcher);
@@ -15926,7 +17021,7 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15926
17021
  addAndConvert(cont, KEY_RAW, rawEmitter);
15927
17022
  } else {
15928
17023
  watcher = createFsWatchInstance(
15929
- path30,
17024
+ path31,
15930
17025
  options,
15931
17026
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
15932
17027
  errHandler,
@@ -15941,7 +17036,7 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15941
17036
  cont.watcherUnusable = true;
15942
17037
  if (isWindows && error.code === "EPERM") {
15943
17038
  try {
15944
- const fd = await (0, import_promises2.open)(path30, "r");
17039
+ const fd = await (0, import_promises2.open)(path31, "r");
15945
17040
  await fd.close();
15946
17041
  broadcastErr(error);
15947
17042
  } catch (err) {
@@ -15972,12 +17067,12 @@ var setFsWatchListener = (path30, fullPath, options, handlers) => {
15972
17067
  };
15973
17068
  };
15974
17069
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
15975
- var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
17070
+ var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
15976
17071
  const { listener, rawEmitter } = handlers;
15977
17072
  let cont = FsWatchFileInstances.get(fullPath);
15978
17073
  const copts = cont && cont.options;
15979
17074
  if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
15980
- (0, import_node_fs.unwatchFile)(fullPath);
17075
+ (0, import_node_fs2.unwatchFile)(fullPath);
15981
17076
  cont = void 0;
15982
17077
  }
15983
17078
  if (cont) {
@@ -15988,13 +17083,13 @@ var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
15988
17083
  listeners: listener,
15989
17084
  rawEmitters: rawEmitter,
15990
17085
  options,
15991
- watcher: (0, import_node_fs.watchFile)(fullPath, options, (curr, prev) => {
17086
+ watcher: (0, import_node_fs2.watchFile)(fullPath, options, (curr, prev) => {
15992
17087
  foreach(cont.rawEmitters, (rawEmitter2) => {
15993
17088
  rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
15994
17089
  });
15995
17090
  const currmtime = curr.mtimeMs;
15996
17091
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
15997
- foreach(cont.listeners, (listener2) => listener2(path30, curr));
17092
+ foreach(cont.listeners, (listener2) => listener2(path31, curr));
15998
17093
  }
15999
17094
  })
16000
17095
  };
@@ -16005,7 +17100,7 @@ var setFsWatchFileListener = (path30, fullPath, options, handlers) => {
16005
17100
  delFromSet(cont, KEY_RAW, rawEmitter);
16006
17101
  if (isEmptySet(cont.listeners)) {
16007
17102
  FsWatchFileInstances.delete(fullPath);
16008
- (0, import_node_fs.unwatchFile)(fullPath);
17103
+ (0, import_node_fs2.unwatchFile)(fullPath);
16009
17104
  cont.options = cont.watcher = void 0;
16010
17105
  Object.freeze(cont);
16011
17106
  }
@@ -16024,13 +17119,13 @@ var NodeFsHandler = class {
16024
17119
  * @param listener on fs change
16025
17120
  * @returns closer for the watcher instance
16026
17121
  */
16027
- _watchWithNodeFs(path30, listener) {
17122
+ _watchWithNodeFs(path31, listener) {
16028
17123
  const opts = this.fsw.options;
16029
- const directory = sp.dirname(path30);
16030
- const basename9 = sp.basename(path30);
17124
+ const directory = sp.dirname(path31);
17125
+ const basename10 = sp.basename(path31);
16031
17126
  const parent = this.fsw._getWatchedDir(directory);
16032
- parent.add(basename9);
16033
- const absolutePath = sp.resolve(path30);
17127
+ parent.add(basename10);
17128
+ const absolutePath = sp.resolve(path31);
16034
17129
  const options = {
16035
17130
  persistent: opts.persistent
16036
17131
  };
@@ -16039,13 +17134,13 @@ var NodeFsHandler = class {
16039
17134
  let closer;
16040
17135
  if (opts.usePolling) {
16041
17136
  const enableBin = opts.interval !== opts.binaryInterval;
16042
- options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
16043
- closer = setFsWatchFileListener(path30, absolutePath, options, {
17137
+ options.interval = enableBin && isBinaryPath(basename10) ? opts.binaryInterval : opts.interval;
17138
+ closer = setFsWatchFileListener(path31, absolutePath, options, {
16044
17139
  listener,
16045
17140
  rawEmitter: this.fsw._emitRaw
16046
17141
  });
16047
17142
  } else {
16048
- closer = setFsWatchListener(path30, absolutePath, options, {
17143
+ closer = setFsWatchListener(path31, absolutePath, options, {
16049
17144
  listener,
16050
17145
  errHandler: this._boundHandleError,
16051
17146
  rawEmitter: this.fsw._emitRaw
@@ -16061,13 +17156,13 @@ var NodeFsHandler = class {
16061
17156
  if (this.fsw.closed) {
16062
17157
  return;
16063
17158
  }
16064
- const dirname15 = sp.dirname(file);
16065
- const basename9 = sp.basename(file);
16066
- const parent = this.fsw._getWatchedDir(dirname15);
17159
+ const dirname16 = sp.dirname(file);
17160
+ const basename10 = sp.basename(file);
17161
+ const parent = this.fsw._getWatchedDir(dirname16);
16067
17162
  let prevStats = stats;
16068
- if (parent.has(basename9))
17163
+ if (parent.has(basename10))
16069
17164
  return;
16070
- const listener = async (path30, newStats) => {
17165
+ const listener = async (path31, newStats) => {
16071
17166
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
16072
17167
  return;
16073
17168
  if (!newStats || newStats.mtimeMs === 0) {
@@ -16081,18 +17176,18 @@ var NodeFsHandler = class {
16081
17176
  this.fsw._emit(EV.CHANGE, file, newStats2);
16082
17177
  }
16083
17178
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
16084
- this.fsw._closeFile(path30);
17179
+ this.fsw._closeFile(path31);
16085
17180
  prevStats = newStats2;
16086
17181
  const closer2 = this._watchWithNodeFs(file, listener);
16087
17182
  if (closer2)
16088
- this.fsw._addPathCloser(path30, closer2);
17183
+ this.fsw._addPathCloser(path31, closer2);
16089
17184
  } else {
16090
17185
  prevStats = newStats2;
16091
17186
  }
16092
17187
  } catch (error) {
16093
- this.fsw._remove(dirname15, basename9);
17188
+ this.fsw._remove(dirname16, basename10);
16094
17189
  }
16095
- } else if (parent.has(basename9)) {
17190
+ } else if (parent.has(basename10)) {
16096
17191
  const at = newStats.atimeMs;
16097
17192
  const mt = newStats.mtimeMs;
16098
17193
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -16117,7 +17212,7 @@ var NodeFsHandler = class {
16117
17212
  * @param item basename of this item
16118
17213
  * @returns true if no more processing is needed for this entry.
16119
17214
  */
16120
- async _handleSymlink(entry, directory, path30, item) {
17215
+ async _handleSymlink(entry, directory, path31, item) {
16121
17216
  if (this.fsw.closed) {
16122
17217
  return;
16123
17218
  }
@@ -16127,7 +17222,7 @@ var NodeFsHandler = class {
16127
17222
  this.fsw._incrReadyCount();
16128
17223
  let linkPath;
16129
17224
  try {
16130
- linkPath = await (0, import_promises2.realpath)(path30);
17225
+ linkPath = await (0, import_promises2.realpath)(path31);
16131
17226
  } catch (e) {
16132
17227
  this.fsw._emitReady();
16133
17228
  return true;
@@ -16137,12 +17232,12 @@ var NodeFsHandler = class {
16137
17232
  if (dir.has(item)) {
16138
17233
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
16139
17234
  this.fsw._symlinkPaths.set(full, linkPath);
16140
- this.fsw._emit(EV.CHANGE, path30, entry.stats);
17235
+ this.fsw._emit(EV.CHANGE, path31, entry.stats);
16141
17236
  }
16142
17237
  } else {
16143
17238
  dir.add(item);
16144
17239
  this.fsw._symlinkPaths.set(full, linkPath);
16145
- this.fsw._emit(EV.ADD, path30, entry.stats);
17240
+ this.fsw._emit(EV.ADD, path31, entry.stats);
16146
17241
  }
16147
17242
  this.fsw._emitReady();
16148
17243
  return true;
@@ -16172,9 +17267,9 @@ var NodeFsHandler = class {
16172
17267
  return;
16173
17268
  }
16174
17269
  const item = entry.path;
16175
- let path30 = sp.join(directory, item);
17270
+ let path31 = sp.join(directory, item);
16176
17271
  current.add(item);
16177
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path30, item)) {
17272
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
16178
17273
  return;
16179
17274
  }
16180
17275
  if (this.fsw.closed) {
@@ -16183,11 +17278,11 @@ var NodeFsHandler = class {
16183
17278
  }
16184
17279
  if (item === target || !target && !previous.has(item)) {
16185
17280
  this.fsw._incrReadyCount();
16186
- path30 = sp.join(dir, sp.relative(dir, path30));
16187
- this._addToNodeFs(path30, initialAdd, wh, depth + 1);
17281
+ path31 = sp.join(dir, sp.relative(dir, path31));
17282
+ this._addToNodeFs(path31, initialAdd, wh, depth + 1);
16188
17283
  }
16189
17284
  }).on(EV.ERROR, this._boundHandleError);
16190
- return new Promise((resolve17, reject) => {
17285
+ return new Promise((resolve18, reject) => {
16191
17286
  if (!stream)
16192
17287
  return reject();
16193
17288
  stream.once(STR_END, () => {
@@ -16196,7 +17291,7 @@ var NodeFsHandler = class {
16196
17291
  return;
16197
17292
  }
16198
17293
  const wasThrottled = throttler ? throttler.clear() : false;
16199
- resolve17(void 0);
17294
+ resolve18(void 0);
16200
17295
  previous.getChildren().filter((item) => {
16201
17296
  return item !== directory && !current.has(item);
16202
17297
  }).forEach((item) => {
@@ -16253,13 +17348,13 @@ var NodeFsHandler = class {
16253
17348
  * @param depth Child path actually targeted for watch
16254
17349
  * @param target Child path actually targeted for watch
16255
17350
  */
16256
- async _addToNodeFs(path30, initialAdd, priorWh, depth, target) {
17351
+ async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
16257
17352
  const ready = this.fsw._emitReady;
16258
- if (this.fsw._isIgnored(path30) || this.fsw.closed) {
17353
+ if (this.fsw._isIgnored(path31) || this.fsw.closed) {
16259
17354
  ready();
16260
17355
  return false;
16261
17356
  }
16262
- const wh = this.fsw._getWatchHelpers(path30);
17357
+ const wh = this.fsw._getWatchHelpers(path31);
16263
17358
  if (priorWh) {
16264
17359
  wh.filterPath = (entry) => priorWh.filterPath(entry);
16265
17360
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -16275,8 +17370,8 @@ var NodeFsHandler = class {
16275
17370
  const follow = this.fsw.options.followSymlinks;
16276
17371
  let closer;
16277
17372
  if (stats.isDirectory()) {
16278
- const absPath = sp.resolve(path30);
16279
- const targetPath = follow ? await (0, import_promises2.realpath)(path30) : path30;
17373
+ const absPath = sp.resolve(path31);
17374
+ const targetPath = follow ? await (0, import_promises2.realpath)(path31) : path31;
16280
17375
  if (this.fsw.closed)
16281
17376
  return;
16282
17377
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -16286,29 +17381,29 @@ var NodeFsHandler = class {
16286
17381
  this.fsw._symlinkPaths.set(absPath, targetPath);
16287
17382
  }
16288
17383
  } else if (stats.isSymbolicLink()) {
16289
- const targetPath = follow ? await (0, import_promises2.realpath)(path30) : path30;
17384
+ const targetPath = follow ? await (0, import_promises2.realpath)(path31) : path31;
16290
17385
  if (this.fsw.closed)
16291
17386
  return;
16292
17387
  const parent = sp.dirname(wh.watchPath);
16293
17388
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
16294
17389
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
16295
- closer = await this._handleDir(parent, stats, initialAdd, depth, path30, wh, targetPath);
17390
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
16296
17391
  if (this.fsw.closed)
16297
17392
  return;
16298
17393
  if (targetPath !== void 0) {
16299
- this.fsw._symlinkPaths.set(sp.resolve(path30), targetPath);
17394
+ this.fsw._symlinkPaths.set(sp.resolve(path31), targetPath);
16300
17395
  }
16301
17396
  } else {
16302
17397
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
16303
17398
  }
16304
17399
  ready();
16305
17400
  if (closer)
16306
- this.fsw._addPathCloser(path30, closer);
17401
+ this.fsw._addPathCloser(path31, closer);
16307
17402
  return false;
16308
17403
  } catch (error) {
16309
17404
  if (this.fsw._handleError(error)) {
16310
17405
  ready();
16311
- return path30;
17406
+ return path31;
16312
17407
  }
16313
17408
  }
16314
17409
  }
@@ -16351,24 +17446,24 @@ function createPattern(matcher) {
16351
17446
  }
16352
17447
  return () => false;
16353
17448
  }
16354
- function normalizePath2(path30) {
16355
- if (typeof path30 !== "string")
17449
+ function normalizePath2(path31) {
17450
+ if (typeof path31 !== "string")
16356
17451
  throw new Error("string expected");
16357
- path30 = sp2.normalize(path30);
16358
- path30 = path30.replace(/\\/g, "/");
17452
+ path31 = sp2.normalize(path31);
17453
+ path31 = path31.replace(/\\/g, "/");
16359
17454
  let prepend = false;
16360
- if (path30.startsWith("//"))
17455
+ if (path31.startsWith("//"))
16361
17456
  prepend = true;
16362
- path30 = path30.replace(DOUBLE_SLASH_RE, "/");
17457
+ path31 = path31.replace(DOUBLE_SLASH_RE, "/");
16363
17458
  if (prepend)
16364
- path30 = "/" + path30;
16365
- return path30;
17459
+ path31 = "/" + path31;
17460
+ return path31;
16366
17461
  }
16367
17462
  function matchPatterns(patterns, testString, stats) {
16368
- const path30 = normalizePath2(testString);
17463
+ const path31 = normalizePath2(testString);
16369
17464
  for (let index = 0; index < patterns.length; index++) {
16370
17465
  const pattern = patterns[index];
16371
- if (pattern(path30, stats)) {
17466
+ if (pattern(path31, stats)) {
16372
17467
  return true;
16373
17468
  }
16374
17469
  }
@@ -16406,19 +17501,19 @@ var toUnix = (string) => {
16406
17501
  }
16407
17502
  return str;
16408
17503
  };
16409
- var normalizePathToUnix = (path30) => toUnix(sp2.normalize(toUnix(path30)));
16410
- var normalizeIgnored = (cwd = "") => (path30) => {
16411
- if (typeof path30 === "string") {
16412
- return normalizePathToUnix(sp2.isAbsolute(path30) ? path30 : sp2.join(cwd, path30));
17504
+ var normalizePathToUnix = (path31) => toUnix(sp2.normalize(toUnix(path31)));
17505
+ var normalizeIgnored = (cwd = "") => (path31) => {
17506
+ if (typeof path31 === "string") {
17507
+ return normalizePathToUnix(sp2.isAbsolute(path31) ? path31 : sp2.join(cwd, path31));
16413
17508
  } else {
16414
- return path30;
17509
+ return path31;
16415
17510
  }
16416
17511
  };
16417
- var getAbsolutePath = (path30, cwd) => {
16418
- if (sp2.isAbsolute(path30)) {
16419
- return path30;
17512
+ var getAbsolutePath = (path31, cwd) => {
17513
+ if (sp2.isAbsolute(path31)) {
17514
+ return path31;
16420
17515
  }
16421
- return sp2.join(cwd, path30);
17516
+ return sp2.join(cwd, path31);
16422
17517
  };
16423
17518
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
16424
17519
  var DirEntry = class {
@@ -16483,10 +17578,10 @@ var WatchHelper = class {
16483
17578
  dirParts;
16484
17579
  followSymlinks;
16485
17580
  statMethod;
16486
- constructor(path30, follow, fsw) {
17581
+ constructor(path31, follow, fsw) {
16487
17582
  this.fsw = fsw;
16488
- const watchPath = path30;
16489
- this.path = path30 = path30.replace(REPLACER_RE, "");
17583
+ const watchPath = path31;
17584
+ this.path = path31 = path31.replace(REPLACER_RE, "");
16490
17585
  this.watchPath = watchPath;
16491
17586
  this.fullWatchPath = sp2.resolve(watchPath);
16492
17587
  this.dirParts = [];
@@ -16626,20 +17721,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16626
17721
  this._closePromise = void 0;
16627
17722
  let paths = unifyPaths(paths_);
16628
17723
  if (cwd) {
16629
- paths = paths.map((path30) => {
16630
- const absPath = getAbsolutePath(path30, cwd);
17724
+ paths = paths.map((path31) => {
17725
+ const absPath = getAbsolutePath(path31, cwd);
16631
17726
  return absPath;
16632
17727
  });
16633
17728
  }
16634
- paths.forEach((path30) => {
16635
- this._removeIgnoredPath(path30);
17729
+ paths.forEach((path31) => {
17730
+ this._removeIgnoredPath(path31);
16636
17731
  });
16637
17732
  this._userIgnored = void 0;
16638
17733
  if (!this._readyCount)
16639
17734
  this._readyCount = 0;
16640
17735
  this._readyCount += paths.length;
16641
- Promise.all(paths.map(async (path30) => {
16642
- const res = await this._nodeFsHandler._addToNodeFs(path30, !_internal, void 0, 0, _origAdd);
17736
+ Promise.all(paths.map(async (path31) => {
17737
+ const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
16643
17738
  if (res)
16644
17739
  this._emitReady();
16645
17740
  return res;
@@ -16661,17 +17756,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16661
17756
  return this;
16662
17757
  const paths = unifyPaths(paths_);
16663
17758
  const { cwd } = this.options;
16664
- paths.forEach((path30) => {
16665
- if (!sp2.isAbsolute(path30) && !this._closers.has(path30)) {
17759
+ paths.forEach((path31) => {
17760
+ if (!sp2.isAbsolute(path31) && !this._closers.has(path31)) {
16666
17761
  if (cwd)
16667
- path30 = sp2.join(cwd, path30);
16668
- path30 = sp2.resolve(path30);
17762
+ path31 = sp2.join(cwd, path31);
17763
+ path31 = sp2.resolve(path31);
16669
17764
  }
16670
- this._closePath(path30);
16671
- this._addIgnoredPath(path30);
16672
- if (this._watched.has(path30)) {
17765
+ this._closePath(path31);
17766
+ this._addIgnoredPath(path31);
17767
+ if (this._watched.has(path31)) {
16673
17768
  this._addIgnoredPath({
16674
- path: path30,
17769
+ path: path31,
16675
17770
  recursive: true
16676
17771
  });
16677
17772
  }
@@ -16735,38 +17830,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16735
17830
  * @param stats arguments to be passed with event
16736
17831
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
16737
17832
  */
16738
- async _emit(event, path30, stats) {
17833
+ async _emit(event, path31, stats) {
16739
17834
  if (this.closed)
16740
17835
  return;
16741
17836
  const opts = this.options;
16742
17837
  if (isWindows)
16743
- path30 = sp2.normalize(path30);
17838
+ path31 = sp2.normalize(path31);
16744
17839
  if (opts.cwd)
16745
- path30 = sp2.relative(opts.cwd, path30);
16746
- const args = [path30];
17840
+ path31 = sp2.relative(opts.cwd, path31);
17841
+ const args = [path31];
16747
17842
  if (stats != null)
16748
17843
  args.push(stats);
16749
17844
  const awf = opts.awaitWriteFinish;
16750
17845
  let pw;
16751
- if (awf && (pw = this._pendingWrites.get(path30))) {
17846
+ if (awf && (pw = this._pendingWrites.get(path31))) {
16752
17847
  pw.lastChange = /* @__PURE__ */ new Date();
16753
17848
  return this;
16754
17849
  }
16755
17850
  if (opts.atomic) {
16756
17851
  if (event === EVENTS.UNLINK) {
16757
- this._pendingUnlinks.set(path30, [event, ...args]);
17852
+ this._pendingUnlinks.set(path31, [event, ...args]);
16758
17853
  setTimeout(() => {
16759
- this._pendingUnlinks.forEach((entry, path31) => {
17854
+ this._pendingUnlinks.forEach((entry, path32) => {
16760
17855
  this.emit(...entry);
16761
17856
  this.emit(EVENTS.ALL, ...entry);
16762
- this._pendingUnlinks.delete(path31);
17857
+ this._pendingUnlinks.delete(path32);
16763
17858
  });
16764
17859
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
16765
17860
  return this;
16766
17861
  }
16767
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path30)) {
17862
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
16768
17863
  event = EVENTS.CHANGE;
16769
- this._pendingUnlinks.delete(path30);
17864
+ this._pendingUnlinks.delete(path31);
16770
17865
  }
16771
17866
  }
16772
17867
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -16784,16 +17879,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16784
17879
  this.emitWithAll(event, args);
16785
17880
  }
16786
17881
  };
16787
- this._awaitWriteFinish(path30, awf.stabilityThreshold, event, awfEmit);
17882
+ this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
16788
17883
  return this;
16789
17884
  }
16790
17885
  if (event === EVENTS.CHANGE) {
16791
- const isThrottled = !this._throttle(EVENTS.CHANGE, path30, 50);
17886
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
16792
17887
  if (isThrottled)
16793
17888
  return this;
16794
17889
  }
16795
17890
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
16796
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path30) : path30;
17891
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path31) : path31;
16797
17892
  let stats2;
16798
17893
  try {
16799
17894
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -16824,23 +17919,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16824
17919
  * @param timeout duration of time to suppress duplicate actions
16825
17920
  * @returns tracking object or false if action should be suppressed
16826
17921
  */
16827
- _throttle(actionType, path30, timeout) {
17922
+ _throttle(actionType, path31, timeout) {
16828
17923
  if (!this._throttled.has(actionType)) {
16829
17924
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
16830
17925
  }
16831
17926
  const action = this._throttled.get(actionType);
16832
17927
  if (!action)
16833
17928
  throw new Error("invalid throttle");
16834
- const actionPath = action.get(path30);
17929
+ const actionPath = action.get(path31);
16835
17930
  if (actionPath) {
16836
17931
  actionPath.count++;
16837
17932
  return false;
16838
17933
  }
16839
17934
  let timeoutObject;
16840
17935
  const clear = () => {
16841
- const item = action.get(path30);
17936
+ const item = action.get(path31);
16842
17937
  const count = item ? item.count : 0;
16843
- action.delete(path30);
17938
+ action.delete(path31);
16844
17939
  clearTimeout(timeoutObject);
16845
17940
  if (item)
16846
17941
  clearTimeout(item.timeoutObject);
@@ -16848,7 +17943,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16848
17943
  };
16849
17944
  timeoutObject = setTimeout(clear, timeout);
16850
17945
  const thr = { timeoutObject, clear, count: 0 };
16851
- action.set(path30, thr);
17946
+ action.set(path31, thr);
16852
17947
  return thr;
16853
17948
  }
16854
17949
  _incrReadyCount() {
@@ -16862,44 +17957,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16862
17957
  * @param event
16863
17958
  * @param awfEmit Callback to be called when ready for event to be emitted.
16864
17959
  */
16865
- _awaitWriteFinish(path30, threshold, event, awfEmit) {
17960
+ _awaitWriteFinish(path31, threshold, event, awfEmit) {
16866
17961
  const awf = this.options.awaitWriteFinish;
16867
17962
  if (typeof awf !== "object")
16868
17963
  return;
16869
17964
  const pollInterval = awf.pollInterval;
16870
17965
  let timeoutHandler;
16871
- let fullPath = path30;
16872
- if (this.options.cwd && !sp2.isAbsolute(path30)) {
16873
- fullPath = sp2.join(this.options.cwd, path30);
17966
+ let fullPath = path31;
17967
+ if (this.options.cwd && !sp2.isAbsolute(path31)) {
17968
+ fullPath = sp2.join(this.options.cwd, path31);
16874
17969
  }
16875
17970
  const now2 = /* @__PURE__ */ new Date();
16876
17971
  const writes = this._pendingWrites;
16877
17972
  function awaitWriteFinishFn(prevStat) {
16878
- (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
16879
- if (err || !writes.has(path30)) {
17973
+ (0, import_node_fs3.stat)(fullPath, (err, curStat) => {
17974
+ if (err || !writes.has(path31)) {
16880
17975
  if (err && err.code !== "ENOENT")
16881
17976
  awfEmit(err);
16882
17977
  return;
16883
17978
  }
16884
17979
  const now3 = Number(/* @__PURE__ */ new Date());
16885
17980
  if (prevStat && curStat.size !== prevStat.size) {
16886
- writes.get(path30).lastChange = now3;
17981
+ writes.get(path31).lastChange = now3;
16887
17982
  }
16888
- const pw = writes.get(path30);
17983
+ const pw = writes.get(path31);
16889
17984
  const df = now3 - pw.lastChange;
16890
17985
  if (df >= threshold) {
16891
- writes.delete(path30);
17986
+ writes.delete(path31);
16892
17987
  awfEmit(void 0, curStat);
16893
17988
  } else {
16894
17989
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
16895
17990
  }
16896
17991
  });
16897
17992
  }
16898
- if (!writes.has(path30)) {
16899
- writes.set(path30, {
17993
+ if (!writes.has(path31)) {
17994
+ writes.set(path31, {
16900
17995
  lastChange: now2,
16901
17996
  cancelWait: () => {
16902
- writes.delete(path30);
17997
+ writes.delete(path31);
16903
17998
  clearTimeout(timeoutHandler);
16904
17999
  return event;
16905
18000
  }
@@ -16910,8 +18005,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16910
18005
  /**
16911
18006
  * Determines whether user has asked to ignore this path.
16912
18007
  */
16913
- _isIgnored(path30, stats) {
16914
- if (this.options.atomic && DOT_RE.test(path30))
18008
+ _isIgnored(path31, stats) {
18009
+ if (this.options.atomic && DOT_RE.test(path31))
16915
18010
  return true;
16916
18011
  if (!this._userIgnored) {
16917
18012
  const { cwd } = this.options;
@@ -16921,17 +18016,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16921
18016
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
16922
18017
  this._userIgnored = anymatch(list, void 0);
16923
18018
  }
16924
- return this._userIgnored(path30, stats);
18019
+ return this._userIgnored(path31, stats);
16925
18020
  }
16926
- _isntIgnored(path30, stat5) {
16927
- return !this._isIgnored(path30, stat5);
18021
+ _isntIgnored(path31, stat5) {
18022
+ return !this._isIgnored(path31, stat5);
16928
18023
  }
16929
18024
  /**
16930
18025
  * Provides a set of common helpers and properties relating to symlink handling.
16931
18026
  * @param path file or directory pattern being watched
16932
18027
  */
16933
- _getWatchHelpers(path30) {
16934
- return new WatchHelper(path30, this.options.followSymlinks, this);
18028
+ _getWatchHelpers(path31) {
18029
+ return new WatchHelper(path31, this.options.followSymlinks, this);
16935
18030
  }
16936
18031
  // Directory helpers
16937
18032
  // -----------------
@@ -16963,63 +18058,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
16963
18058
  * @param item base path of item/directory
16964
18059
  */
16965
18060
  _remove(directory, item, isDirectory) {
16966
- const path30 = sp2.join(directory, item);
16967
- const fullPath = sp2.resolve(path30);
16968
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path30) || this._watched.has(fullPath);
16969
- if (!this._throttle("remove", path30, 100))
18061
+ const path31 = sp2.join(directory, item);
18062
+ const fullPath = sp2.resolve(path31);
18063
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
18064
+ if (!this._throttle("remove", path31, 100))
16970
18065
  return;
16971
18066
  if (!isDirectory && this._watched.size === 1) {
16972
18067
  this.add(directory, item, true);
16973
18068
  }
16974
- const wp = this._getWatchedDir(path30);
18069
+ const wp = this._getWatchedDir(path31);
16975
18070
  const nestedDirectoryChildren = wp.getChildren();
16976
- nestedDirectoryChildren.forEach((nested) => this._remove(path30, nested));
18071
+ nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
16977
18072
  const parent = this._getWatchedDir(directory);
16978
18073
  const wasTracked = parent.has(item);
16979
18074
  parent.remove(item);
16980
18075
  if (this._symlinkPaths.has(fullPath)) {
16981
18076
  this._symlinkPaths.delete(fullPath);
16982
18077
  }
16983
- let relPath = path30;
18078
+ let relPath = path31;
16984
18079
  if (this.options.cwd)
16985
- relPath = sp2.relative(this.options.cwd, path30);
18080
+ relPath = sp2.relative(this.options.cwd, path31);
16986
18081
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
16987
18082
  const event = this._pendingWrites.get(relPath).cancelWait();
16988
18083
  if (event === EVENTS.ADD)
16989
18084
  return;
16990
18085
  }
16991
- this._watched.delete(path30);
18086
+ this._watched.delete(path31);
16992
18087
  this._watched.delete(fullPath);
16993
18088
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
16994
- if (wasTracked && !this._isIgnored(path30))
16995
- this._emit(eventName, path30);
16996
- this._closePath(path30);
18089
+ if (wasTracked && !this._isIgnored(path31))
18090
+ this._emit(eventName, path31);
18091
+ this._closePath(path31);
16997
18092
  }
16998
18093
  /**
16999
18094
  * Closes all watchers for a path
17000
18095
  */
17001
- _closePath(path30) {
17002
- this._closeFile(path30);
17003
- const dir = sp2.dirname(path30);
17004
- this._getWatchedDir(dir).remove(sp2.basename(path30));
18096
+ _closePath(path31) {
18097
+ this._closeFile(path31);
18098
+ const dir = sp2.dirname(path31);
18099
+ this._getWatchedDir(dir).remove(sp2.basename(path31));
17005
18100
  }
17006
18101
  /**
17007
18102
  * Closes only file-specific watchers
17008
18103
  */
17009
- _closeFile(path30) {
17010
- const closers = this._closers.get(path30);
18104
+ _closeFile(path31) {
18105
+ const closers = this._closers.get(path31);
17011
18106
  if (!closers)
17012
18107
  return;
17013
18108
  closers.forEach((closer) => closer());
17014
- this._closers.delete(path30);
18109
+ this._closers.delete(path31);
17015
18110
  }
17016
- _addPathCloser(path30, closer) {
18111
+ _addPathCloser(path31, closer) {
17017
18112
  if (!closer)
17018
18113
  return;
17019
- let list = this._closers.get(path30);
18114
+ let list = this._closers.get(path31);
17020
18115
  if (!list) {
17021
18116
  list = [];
17022
- this._closers.set(path30, list);
18117
+ this._closers.set(path31, list);
17023
18118
  }
17024
18119
  list.push(closer);
17025
18120
  }
@@ -17049,11 +18144,11 @@ function watch(paths, options = {}) {
17049
18144
  var chokidar_default = { watch, FSWatcher };
17050
18145
 
17051
18146
  // src/watcher/file-watcher.ts
17052
- var path23 = __toESM(require("path"), 1);
18147
+ var path24 = __toESM(require("path"), 1);
17053
18148
 
17054
18149
  // src/watcher/native-recursive-watcher.ts
17055
- var import_node_fs3 = require("fs");
17056
- var path21 = __toESM(require("path"), 1);
18150
+ var import_node_fs4 = require("fs");
18151
+ var path22 = __toESM(require("path"), 1);
17057
18152
  var NativeRecursiveWatcher = class {
17058
18153
  constructor(root, onChange, options = {}) {
17059
18154
  this.root = root;
@@ -17101,26 +18196,26 @@ var NativeRecursiveWatcher = class {
17101
18196
  toAbsolutePath(filename) {
17102
18197
  if (filename == null) return null;
17103
18198
  const normalizedFilename = typeof filename === "string" ? filename : filename.toString();
17104
- const absolutePath = path21.resolve(this.root, normalizedFilename);
17105
- const relativePath = path21.relative(this.root, absolutePath);
17106
- const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path21.sep}`) || path21.isAbsolute(relativePath);
18199
+ const absolutePath = path22.resolve(this.root, normalizedFilename);
18200
+ const relativePath = path22.relative(this.root, absolutePath);
18201
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path22.sep}`) || path22.isAbsolute(relativePath);
17107
18202
  return outsideRoot ? null : absolutePath;
17108
18203
  }
17109
- defaultWatchFactory = (root, listener, options) => (0, import_node_fs3.watch)(root, options, listener);
18204
+ defaultWatchFactory = (root, listener, options) => (0, import_node_fs4.watch)(root, options, listener);
17110
18205
  };
17111
18206
 
17112
18207
  // src/watcher/snapshot.ts
17113
18208
  var fsPromises4 = __toESM(require("fs/promises"), 1);
17114
- var path22 = __toESM(require("path"), 1);
18209
+ var path23 = __toESM(require("path"), 1);
17115
18210
  async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17116
- const normalizedProjectRoot = path22.resolve(projectRoot);
18211
+ const normalizedProjectRoot = path23.resolve(projectRoot);
17117
18212
  const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17118
18213
  const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17119
18214
  const maxDepth = config.indexing?.maxDepth ?? -1;
17120
18215
  const snapshot = /* @__PURE__ */ new Map();
17121
18216
  const unreadablePrefixes = /* @__PURE__ */ new Set();
17122
18217
  const includeFile = async (filePath) => {
17123
- const normalizedPath2 = path22.resolve(filePath);
18218
+ const normalizedPath2 = path23.resolve(filePath);
17124
18219
  if (!shouldIncludeFile(normalizedPath2, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
17125
18220
  const stat5 = await readStatIfFile(normalizedPath2, unreadablePrefixes);
17126
18221
  if (stat5) snapshot.set(normalizedPath2, { size: stat5.size, mtimeMs: stat5.mtimeMs });
@@ -17132,16 +18227,16 @@ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17132
18227
  } catch (error) {
17133
18228
  if (isMissingFsError(error)) return;
17134
18229
  if (isPermissionFsError(error)) {
17135
- unreadablePrefixes.add(path22.resolve(directoryPath));
18230
+ unreadablePrefixes.add(path23.resolve(directoryPath));
17136
18231
  return;
17137
18232
  }
17138
18233
  throw error;
17139
18234
  }
17140
18235
  for (const entry of entries) {
17141
- const fullPath = path22.join(directoryPath, entry.name);
17142
- const relativePath = path22.relative(normalizedProjectRoot, fullPath);
18236
+ const fullPath = path23.join(directoryPath, entry.name);
18237
+ const relativePath = path23.relative(normalizedProjectRoot, fullPath);
17143
18238
  if (entry.isDirectory()) {
17144
- if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
18239
+ if (hasFilteredPathSegment(relativePath, path23.sep) || isRestrictedDirectory(relativePath, path23.sep)) continue;
17145
18240
  if (ignoreFilter.ignores(relativePath)) continue;
17146
18241
  if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17147
18242
  } else if (entry.isFile()) {
@@ -17154,19 +18249,19 @@ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
17154
18249
  return { entries: snapshot, unreadablePrefixes };
17155
18250
  }
17156
18251
  async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
17157
- const normalizedProjectRoot = path22.resolve(projectRoot);
17158
- const normalizedTargetPath = path22.resolve(targetPath);
18252
+ const normalizedProjectRoot = path23.resolve(projectRoot);
18253
+ const normalizedTargetPath = path23.resolve(targetPath);
17159
18254
  if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
17160
18255
  return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
17161
18256
  }
17162
18257
  const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
17163
18258
  const includePatterns = [...config.include, ...config.additionalInclude ?? []];
17164
18259
  const maxDepth = config.indexing?.maxDepth ?? -1;
17165
- const explicitConfigPaths = new Set(configPaths.map((configPath) => path22.resolve(configPath)));
18260
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path23.resolve(configPath)));
17166
18261
  const snapshot = /* @__PURE__ */ new Map();
17167
18262
  const unreadablePrefixes = /* @__PURE__ */ new Set();
17168
18263
  const includeFile = async (filePath) => {
17169
- const normalizedPath2 = path22.resolve(filePath);
18264
+ const normalizedPath2 = path23.resolve(filePath);
17170
18265
  if (!explicitConfigPaths.has(normalizedPath2) && !shouldIncludeFile(
17171
18266
  normalizedPath2,
17172
18267
  normalizedProjectRoot,
@@ -17184,16 +18279,16 @@ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, ta
17184
18279
  } catch (error) {
17185
18280
  if (isMissingFsError(error)) return;
17186
18281
  if (isPermissionFsError(error)) {
17187
- unreadablePrefixes.add(path22.resolve(directoryPath));
18282
+ unreadablePrefixes.add(path23.resolve(directoryPath));
17188
18283
  return;
17189
18284
  }
17190
18285
  throw error;
17191
18286
  }
17192
18287
  for (const entry of entries) {
17193
- const fullPath = path22.join(directoryPath, entry.name);
17194
- const relativePath = path22.relative(normalizedProjectRoot, fullPath);
18288
+ const fullPath = path23.join(directoryPath, entry.name);
18289
+ const relativePath = path23.relative(normalizedProjectRoot, fullPath);
17195
18290
  if (entry.isDirectory()) {
17196
- if (hasFilteredPathSegment(relativePath, path22.sep) || isRestrictedDirectory(relativePath, path22.sep)) continue;
18291
+ if (hasFilteredPathSegment(relativePath, path23.sep) || isRestrictedDirectory(relativePath, path23.sep)) continue;
17197
18292
  if (ignoreFilter.ignores(relativePath)) continue;
17198
18293
  if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
17199
18294
  } else if (entry.isFile()) {
@@ -17217,7 +18312,7 @@ function completeFileSnapshot(previous, scan) {
17217
18312
  return completed;
17218
18313
  }
17219
18314
  async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
17220
- for (const configPath of [...new Set(configPaths.map((value) => path22.resolve(value)))]) {
18315
+ for (const configPath of [...new Set(configPaths.map((value) => path23.resolve(value)))]) {
17221
18316
  if (snapshot.has(configPath)) continue;
17222
18317
  const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
17223
18318
  if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
@@ -17227,12 +18322,12 @@ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, co
17227
18322
  await includeExplicitConfigPaths(
17228
18323
  snapshot,
17229
18324
  unreadablePrefixes,
17230
- configPaths.filter((configPath) => isWithinPath(targetPath, path22.resolve(configPath)))
18325
+ configPaths.filter((configPath) => isWithinPath(targetPath, path23.resolve(configPath)))
17231
18326
  );
17232
18327
  }
17233
18328
  function isWithinPath(parentPath, childPath) {
17234
- const relativePath = path22.relative(parentPath, childPath);
17235
- return relativePath === "" || !relativePath.startsWith(`..${path22.sep}`) && relativePath !== ".." && !path22.isAbsolute(relativePath);
18329
+ const relativePath = path23.relative(parentPath, childPath);
18330
+ return relativePath === "" || !relativePath.startsWith(`..${path23.sep}`) && relativePath !== ".." && !path23.isAbsolute(relativePath);
17236
18331
  }
17237
18332
  async function readStatIfFile(filePath, unreadablePrefixes) {
17238
18333
  try {
@@ -17241,7 +18336,7 @@ async function readStatIfFile(filePath, unreadablePrefixes) {
17241
18336
  } catch (error) {
17242
18337
  if (isMissingFsError(error)) return null;
17243
18338
  if (isPermissionFsError(error)) {
17244
- unreadablePrefixes.add(path22.resolve(filePath));
18339
+ unreadablePrefixes.add(path23.resolve(filePath));
17245
18340
  return null;
17246
18341
  }
17247
18342
  throw error;
@@ -17378,8 +18473,8 @@ var FileWatcher = class {
17378
18473
  this.createWatcher();
17379
18474
  }
17380
18475
  resetReady() {
17381
- this.readyPromise = new Promise((resolve17) => {
17382
- this.resolveReady = resolve17;
18476
+ this.readyPromise = new Promise((resolve18) => {
18477
+ this.resolveReady = resolve18;
17383
18478
  });
17384
18479
  this.startupReadySignals = 1;
17385
18480
  }
@@ -17410,7 +18505,7 @@ var FileWatcher = class {
17410
18505
  const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
17411
18506
  const watcherOptions = {
17412
18507
  ignored: (filePath) => {
17413
- const relativePath = path23.relative(this.projectRoot, filePath);
18508
+ const relativePath = path24.relative(this.projectRoot, filePath);
17414
18509
  if (!relativePath) return false;
17415
18510
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
17416
18511
  return false;
@@ -17418,10 +18513,10 @@ var FileWatcher = class {
17418
18513
  if (this.isOutsideProjectPath(relativePath)) {
17419
18514
  return true;
17420
18515
  }
17421
- if (hasFilteredPathSegment(relativePath, path23.sep)) {
18516
+ if (hasFilteredPathSegment(relativePath, path24.sep)) {
17422
18517
  return true;
17423
18518
  }
17424
- if (isRestrictedDirectory(relativePath, path23.sep)) {
18519
+ if (isRestrictedDirectory(relativePath, path24.sep)) {
17425
18520
  return true;
17426
18521
  }
17427
18522
  if (ignoreFilter.ignores(relativePath)) {
@@ -17512,13 +18607,13 @@ var FileWatcher = class {
17512
18607
  getExternalConfigWatchTargets() {
17513
18608
  return [...new Set(
17514
18609
  this.projectConfigPaths.filter((projectConfigPath) => {
17515
- const relativeConfigPath = path23.relative(this.projectRoot, projectConfigPath);
18610
+ const relativeConfigPath = path24.relative(this.projectRoot, projectConfigPath);
17516
18611
  return this.isOutsideProjectPath(relativeConfigPath);
17517
18612
  }).map((projectConfigPath) => {
17518
18613
  if ((0, import_fs14.existsSync)(projectConfigPath)) {
17519
18614
  return projectConfigPath;
17520
18615
  }
17521
- return this.getNearestExistingDirectory(path23.dirname(projectConfigPath));
18616
+ return this.getNearestExistingDirectory(path24.dirname(projectConfigPath));
17522
18617
  })
17523
18618
  )];
17524
18619
  }
@@ -17580,7 +18675,7 @@ var FileWatcher = class {
17580
18675
  }
17581
18676
  scheduleNativeReconciliation(generation, filePath) {
17582
18677
  if (!this.isCurrentNativeSetup(generation)) return;
17583
- const requiresFullReconciliation = filePath === path23.join(this.projectRoot, ".gitignore");
18678
+ const requiresFullReconciliation = filePath === path24.join(this.projectRoot, ".gitignore");
17584
18679
  const invalidatedPath = requiresFullReconciliation ? null : filePath;
17585
18680
  this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
17586
18681
  if (this.nativeReconcileTimer) {
@@ -17675,23 +18770,23 @@ var FileWatcher = class {
17675
18770
  this.scheduleFlush();
17676
18771
  }
17677
18772
  isProjectConfigPath(filePath) {
17678
- const relativePath = path23.relative(this.projectRoot, filePath);
17679
- const normalizedRelativePath = path23.normalize(relativePath);
18773
+ const relativePath = path24.relative(this.projectRoot, filePath);
18774
+ const normalizedRelativePath = path24.normalize(relativePath);
17680
18775
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
17681
18776
  }
17682
18777
  isProjectConfigPathOrAncestor(relativePath) {
17683
- const normalizedRelativePath = path23.normalize(relativePath);
18778
+ const normalizedRelativePath = path24.normalize(relativePath);
17684
18779
  return this.getProjectConfigRelativePaths().some(
17685
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path23.sep}`)
18780
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path24.sep}`)
17686
18781
  );
17687
18782
  }
17688
18783
  isOutsideProjectPath(relativePath) {
17689
- return relativePath === ".." || relativePath.startsWith(`..${path23.sep}`) || path23.isAbsolute(relativePath);
18784
+ return relativePath === ".." || relativePath.startsWith(`..${path24.sep}`) || path24.isAbsolute(relativePath);
17690
18785
  }
17691
18786
  getNearestExistingDirectory(directoryPath) {
17692
18787
  let candidate = directoryPath;
17693
18788
  while (!(0, import_fs14.existsSync)(candidate)) {
17694
- const parent = path23.dirname(candidate);
18789
+ const parent = path24.dirname(candidate);
17695
18790
  if (parent === candidate) break;
17696
18791
  candidate = parent;
17697
18792
  }
@@ -17699,7 +18794,7 @@ var FileWatcher = class {
17699
18794
  }
17700
18795
  getProjectConfigRelativePaths() {
17701
18796
  return this.projectConfigPaths.map(
17702
- (configPath) => path23.normalize(path23.relative(this.projectRoot, configPath))
18797
+ (configPath) => path24.normalize(path24.relative(this.projectRoot, configPath))
17703
18798
  );
17704
18799
  }
17705
18800
  getConfigPathStates() {
@@ -17757,7 +18852,7 @@ var FileWatcher = class {
17757
18852
  return;
17758
18853
  }
17759
18854
  const changes = Array.from(this.pendingChanges.entries()).map(
17760
- ([path30, type]) => ({ path: path30, type })
18855
+ ([path31, type]) => ({ path: path31, type })
17761
18856
  );
17762
18857
  this.pendingChanges.clear();
17763
18858
  try {
@@ -17803,7 +18898,7 @@ var FileWatcher = class {
17803
18898
  };
17804
18899
 
17805
18900
  // src/watcher/git-head-watcher.ts
17806
- var path24 = __toESM(require("path"), 1);
18901
+ var path25 = __toESM(require("path"), 1);
17807
18902
  var GitHeadWatcher = class {
17808
18903
  watcher = null;
17809
18904
  projectRoot;
@@ -17825,13 +18920,13 @@ var GitHeadWatcher = class {
17825
18920
  this.readyPromise = Promise.resolve();
17826
18921
  return;
17827
18922
  }
17828
- this.readyPromise = new Promise((resolve17) => {
17829
- this.resolveReady = resolve17;
18923
+ this.readyPromise = new Promise((resolve18) => {
18924
+ this.resolveReady = resolve18;
17830
18925
  });
17831
18926
  this.onBranchChange = handler;
17832
18927
  this.currentBranch = getCurrentBranch(this.projectRoot);
17833
18928
  const headPath = getHeadPath(this.projectRoot);
17834
- const refsPath = path24.join(this.projectRoot, ".git", "refs", "heads");
18929
+ const refsPath = path25.join(this.projectRoot, ".git", "refs", "heads");
17835
18930
  this.watcher = chokidar_default.watch([headPath, refsPath], {
17836
18931
  persistent: true,
17837
18932
  ignoreInitial: true,
@@ -17899,7 +18994,9 @@ var GitHeadWatcher = class {
17899
18994
  function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options = {}) {
17900
18995
  const fileWatcher = new FileWatcher(projectRoot, config, host, options);
17901
18996
  const configPaths = getConfigPaths(projectRoot, host, options);
17902
- configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer);
18997
+ configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer, {
18998
+ synchronizeBackgroundWorker: false
18999
+ });
17903
19000
  let stopped = false;
17904
19001
  const requestReindex = () => {
17905
19002
  if (stopped) return;
@@ -17919,7 +19016,9 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options
17919
19016
  const parsedConfig = options.configPath ? parseConfig(loadConfigFile(options.configPath)) : void 0;
17920
19017
  const refreshedConfig = refreshIndexerForDirectory(projectRoot, host, parsedConfig);
17921
19018
  if (refreshedConfig) {
17922
- configureAutoIndex(projectRoot, host, refreshedConfig, getIndexer);
19019
+ configureAutoIndex(projectRoot, host, refreshedConfig, getIndexer, {
19020
+ synchronizeBackgroundWorker: false
19021
+ });
17923
19022
  }
17924
19023
  }
17925
19024
  requestReindex();
@@ -18695,7 +19794,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
18695
19794
  const directory = input.directory ?? void 0;
18696
19795
  const tokenBudget = input.tokenBudget ?? void 0;
18697
19796
  if (from && to) {
18698
- const path30 = await getCallGraphPath(
19797
+ const path31 = await getCallGraphPath(
18699
19798
  projectRoot,
18700
19799
  host,
18701
19800
  from,
@@ -18704,25 +19803,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
18704
19803
  fromFilePath,
18705
19804
  toFilePath
18706
19805
  );
18707
- const pathText = formatCallGraphPathResult(path30);
18708
- if (path30.path.length > 0) {
19806
+ const pathText = formatCallGraphPathResult(path31);
19807
+ if (path31.path.length > 0) {
18709
19808
  const fitted2 = fitTextToContextBudget(
18710
19809
  pathText,
18711
19810
  tokenBudget
18712
19811
  );
18713
19812
  return {
18714
19813
  text: fitted2.text,
18715
- details: fittedDetails("path", fitted2, path30.path.length)
19814
+ details: fittedDetails("path", fitted2, path31.path.length)
18716
19815
  };
18717
19816
  }
18718
- if (path30.from.status !== "resolved" || path30.to.status !== "resolved") {
19817
+ if (path31.from.status !== "resolved" || path31.to.status !== "resolved") {
18719
19818
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
18720
19819
  return {
18721
19820
  text: fitted2.text,
18722
19821
  details: fittedDetails("path", fitted2, 0)
18723
19822
  };
18724
19823
  }
18725
- const resolvedFrom = path30.from;
19824
+ const resolvedFrom = path31.from;
18726
19825
  const { callers } = await getCallGraphData(projectRoot, host, {
18727
19826
  name: to,
18728
19827
  direction: "callers",
@@ -18846,6 +19945,7 @@ async function executeCodebaseEditContext(projectRoot, host, args) {
18846
19945
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
18847
19946
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
18848
19947
  if (result.kind === "estimate") return { text: formatCostEstimate(result.estimate) };
19948
+ if (result.kind === "dryrun") return { text: formatDryRunEstimate(result.dryrun) };
18849
19949
  if (result.kind === "busy") return { text: result.text, isError: true };
18850
19950
  if (result.kind === "message") return { text: result.text };
18851
19951
  return { text: formatIndexStats(result.stats, args.verbose ?? false) };
@@ -18882,7 +19982,7 @@ async function executeCallGraph(projectRoot, host, args) {
18882
19982
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
18883
19983
  }
18884
19984
  async function executeCallGraphPath(projectRoot, host, args) {
18885
- const path30 = await getCallGraphPath(
19985
+ const path31 = await getCallGraphPath(
18886
19986
  projectRoot,
18887
19987
  host,
18888
19988
  args.from,
@@ -18891,7 +19991,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
18891
19991
  args.fromFilePath,
18892
19992
  args.toFilePath
18893
19993
  );
18894
- return { text: formatCallGraphPathResult(path30) };
19994
+ return { text: formatCallGraphPathResult(path31) };
18895
19995
  }
18896
19996
  async function executeCodeCommunities(projectRoot, host, args) {
18897
19997
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -18900,12 +20000,12 @@ async function executeCodeCommunities(projectRoot, host, args) {
18900
20000
 
18901
20001
  // src/adapters/opencode/tools.ts
18902
20002
  var import_fs15 = require("fs");
18903
- var os7 = __toESM(require("os"), 1);
18904
- var path27 = __toESM(require("path"), 1);
20003
+ var os8 = __toESM(require("os"), 1);
20004
+ var path28 = __toESM(require("path"), 1);
18905
20005
 
18906
20006
  // src/tools/visualize/activity.ts
18907
20007
  var import_child_process5 = require("child_process");
18908
- var path25 = __toESM(require("path"), 1);
20008
+ var path26 = __toESM(require("path"), 1);
18909
20009
  function attachRecentActivity(data, projectRoot) {
18910
20010
  const activity = readGitActivity(projectRoot);
18911
20011
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -19067,7 +20167,7 @@ function normalizePath3(filePath) {
19067
20167
  return filePath.replace(/\\/g, "/");
19068
20168
  }
19069
20169
  function toGitRelativePath(projectRoot, filePath) {
19070
- const relativePath = path25.isAbsolute(filePath) ? path25.relative(projectRoot, filePath) : filePath;
20170
+ const relativePath = path26.isAbsolute(filePath) ? path26.relative(projectRoot, filePath) : filePath;
19071
20171
  return normalizePath3(relativePath);
19072
20172
  }
19073
20173
 
@@ -19325,7 +20425,7 @@ render();
19325
20425
  }
19326
20426
 
19327
20427
  // src/tools/visualize/transform.ts
19328
- var path26 = __toESM(require("path"), 1);
20428
+ var path27 = __toESM(require("path"), 1);
19329
20429
 
19330
20430
  // src/tools/visualize/modules.ts
19331
20431
  var MAX_MODULES = 18;
@@ -19585,7 +20685,7 @@ function transformForVisualization(symbols, edges, options = {}) {
19585
20685
  filePath: s.filePath,
19586
20686
  kind: s.kind,
19587
20687
  line: s.startLine,
19588
- directory: path26.dirname(s.filePath),
20688
+ directory: path27.dirname(s.filePath),
19589
20689
  moduleId: "",
19590
20690
  moduleLabel: ""
19591
20691
  }));
@@ -19710,6 +20810,7 @@ var index_codebase = tool({
19710
20810
  args: {
19711
20811
  force: z3.boolean().optional().default(false).describe("Force reindex even if already indexed"),
19712
20812
  estimateOnly: z3.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
20813
+ 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)."),
19713
20814
  verbose: z3.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
19714
20815
  },
19715
20816
  async execute(args, context) {
@@ -19913,7 +21014,7 @@ var index_visualize = tool({
19913
21014
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
19914
21015
  }
19915
21016
  const html = generateVisualizationHtml(vizData);
19916
- const outputPath = path27.join(os7.tmpdir(), `call-graph-${Date.now()}.html`);
21017
+ const outputPath = path28.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
19917
21018
  (0, import_fs15.writeFileSync)(outputPath, html, "utf-8");
19918
21019
  let result = `Temporal call graph visualization generated: ${outputPath}
19919
21020
 
@@ -20026,7 +21127,7 @@ var MCP_TOOL_NAMES = [
20026
21127
 
20027
21128
  // src/commands/loader.ts
20028
21129
  var import_fs16 = require("fs");
20029
- var path28 = __toESM(require("path"), 1);
21130
+ var path29 = __toESM(require("path"), 1);
20030
21131
  function parseFrontmatter(content) {
20031
21132
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
20032
21133
  const match = content.match(frontmatterRegex);
@@ -20052,7 +21153,7 @@ function loadCommandsFromDirectory(commandsDir) {
20052
21153
  }
20053
21154
  const files = (0, import_fs16.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
20054
21155
  for (const file of files) {
20055
- const filePath = path28.join(commandsDir, file);
21156
+ const filePath = path29.join(commandsDir, file);
20056
21157
  let content;
20057
21158
  try {
20058
21159
  content = (0, import_fs16.readFileSync)(filePath, "utf-8");
@@ -20061,7 +21162,7 @@ function loadCommandsFromDirectory(commandsDir) {
20061
21162
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
20062
21163
  }
20063
21164
  const { frontmatter, body } = parseFrontmatter(content);
20064
- const name = path28.basename(file, ".md");
21165
+ const name = path29.basename(file, ".md");
20065
21166
  const description = frontmatter.description || `Run the ${name} command`;
20066
21167
  commands.set(name, {
20067
21168
  description,
@@ -20310,6 +21411,7 @@ function assessRoutingIntent(text) {
20310
21411
  };
20311
21412
  }
20312
21413
  function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
21414
+ const hasSymbolCue = hasIdentifierShape(assessment.text) || containsQuotedIdentifier(assessment.text);
20313
21415
  if (assessment.intent === "definition_lookup") {
20314
21416
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20315
21417
  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.";
@@ -20319,12 +21421,13 @@ function buildRoutingHint(assessment, status, includeGraphHandoff = false) {
20319
21421
  if (assessment.intent !== "local_conceptual" && assessment.intent !== "local_broad_task") {
20320
21422
  return null;
20321
21423
  }
21424
+ 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." : "";
20322
21425
  if (!status || !status.indexed || status.compatibility?.compatible === false) {
20323
21426
  const graphHandoff2 = includeGraphHandoff ? " Use graph tools after semantic discovery identifies relevant symbols." : "";
20324
- 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.`;
21427
+ 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}`;
20325
21428
  }
20326
21429
  const graphHandoff = includeGraphHandoff ? " before graph tools such as `call_graph`, `call_graph_path`, `pr_impact`, or OMO CodeGraph" : "";
20327
- 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.`;
21430
+ 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}`;
20328
21431
  }
20329
21432
  var RoutingHintController = class {
20330
21433
  constructor(getStatus, maxSessions = 200, includeGraphHandoff = false) {
@@ -20366,7 +21469,7 @@ var RoutingHintController = class {
20366
21469
  if (!state || !state.pendingHint) {
20367
21470
  return;
20368
21471
  }
20369
- if (toolName === "codebase_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
21472
+ if (toolName === "codebase_context" || toolName === "codebase_edit_context" || toolName === "codebase_peek" || toolName === "codebase_search" || toolName === "implementation_lookup" || toolName === "index_status" || toolName === "index_codebase") {
20370
21473
  state.pendingHint = false;
20371
21474
  state.updatedAt = Date.now();
20372
21475
  this.sessionState.set(sessionID, state);
@@ -20395,42 +21498,13 @@ var RoutingHintController = class {
20395
21498
 
20396
21499
  // src/adapters/opencode.ts
20397
21500
  var import_meta2 = {};
20398
- var activeWatchers = /* @__PURE__ */ new Map();
20399
- var watcherReplacementChains = /* @__PURE__ */ new Map();
20400
- async function replaceActiveWatcher(projectRoot, createNextWatcher) {
20401
- const chain = (watcherReplacementChains.get(projectRoot) ?? Promise.resolve()).catch(() => void 0).then(async () => {
20402
- const existing = activeWatchers.get(projectRoot);
20403
- if (existing) {
20404
- try {
20405
- await existing.stop();
20406
- } catch (error) {
20407
- console.error("[codebase-index] Failed to stop replaced watcher:", error);
20408
- throw error;
20409
- }
20410
- if (activeWatchers.get(projectRoot) === existing) {
20411
- activeWatchers.delete(projectRoot);
20412
- }
20413
- }
20414
- if (createNextWatcher) {
20415
- activeWatchers.set(projectRoot, createNextWatcher());
20416
- }
20417
- });
20418
- watcherReplacementChains.set(projectRoot, chain);
20419
- try {
20420
- await chain;
20421
- } finally {
20422
- if (watcherReplacementChains.get(projectRoot) === chain) {
20423
- watcherReplacementChains.delete(projectRoot);
20424
- }
20425
- }
20426
- }
20427
21501
  function getCommandsDir() {
20428
21502
  let currentDir = process.cwd();
20429
21503
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
20430
- currentDir = path29.dirname((0, import_url.fileURLToPath)(import_meta2.url));
21504
+ currentDir = path30.dirname((0, import_url.fileURLToPath)(import_meta2.url));
20431
21505
  }
20432
- const packageRoot = path29.basename(currentDir) === "adapters" ? path29.join(currentDir, "..", "..") : path29.join(currentDir, "..");
20433
- return path29.join(packageRoot, "commands");
21506
+ const packageRoot = path30.basename(currentDir) === "adapters" ? path30.join(currentDir, "..", "..") : path30.join(currentDir, "..");
21507
+ return path30.join(packageRoot, "commands");
20434
21508
  }
20435
21509
  function appendRoutingHints(output, hints, preferredRole) {
20436
21510
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -20456,8 +21530,9 @@ var plugin = async ({ directory, worktree }) => {
20456
21530
  initializeTools2(projectRoot, config);
20457
21531
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
20458
21532
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
20459
- const isHomeDir = isHomeDirectory(projectRoot);
20460
- const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
21533
+ const projectSafety = getProjectSafety(projectRoot, config);
21534
+ const isHomeDir = projectSafety.blockedReason === "home-directory";
21535
+ const isValidProject = projectSafety.safeToRun;
20461
21536
  if (isHomeDir) {
20462
21537
  console.warn(
20463
21538
  `[codebase-index] Refusing to watch or index home directory "${projectRoot}". Open a specific project directory instead.`
@@ -20467,16 +21542,24 @@ var plugin = async ({ directory, worktree }) => {
20467
21542
  `[codebase-index] Skipping file watching and auto-indexing: no project marker found in "${projectRoot}". Set "indexing.requireProjectMarker": false in config to override.`
20468
21543
  );
20469
21544
  }
20470
- if (config.indexing.autoIndex && isValidProject) {
20471
- startAutoIndex(projectRoot, "opencode", "startup");
20472
- }
20473
- if (config.indexing.watchFiles && isValidProject) {
20474
- await replaceActiveWatcher(
20475
- projectRoot,
20476
- () => createWatcherWithIndexer(getProjectIndexer, projectRoot, config, "opencode")
20477
- );
21545
+ if (!isValidProject) {
21546
+ await stopBackgroundWorker(projectRoot, "opencode").catch((error) => {
21547
+ console.error("[codebase-index] Failed to stop unsafe OpenCode background worker:", error);
21548
+ });
20478
21549
  } else {
20479
- await replaceActiveWatcher(projectRoot, null);
21550
+ const watcherFactoryForConfig = (refreshedConfig) => refreshedConfig.indexing.watchFiles ? () => createWatcherWithIndexer(getProjectIndexer, projectRoot, refreshedConfig, "opencode") : null;
21551
+ configureBackgroundWorker(projectRoot, "opencode", config, {
21552
+ startAutoIndex: (source, allowDisabledAutoIndex) => {
21553
+ startAutoIndexForBackgroundWorker(projectRoot, "opencode", source, allowDisabledAutoIndex);
21554
+ },
21555
+ stopAutoIndex: () => stopAutoIndexForBackgroundWorker(projectRoot, "opencode"),
21556
+ watcherFactory: watcherFactoryForConfig(config),
21557
+ watcherFactoryForConfig,
21558
+ replaceWatcher: true
21559
+ }, {
21560
+ restartAutoIndex: true
21561
+ });
21562
+ await waitForBackgroundWorkerStart(projectRoot, "opencode");
20480
21563
  }
20481
21564
  return {
20482
21565
  tool: {