opencode-codebase-index 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -495,7 +495,7 @@ var require_ignore = __commonJS({
495
495
  // path matching.
496
496
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
497
497
  // @returns {TestResult} true if a file is ignored
498
- test(path23, checkUnignored, mode) {
498
+ test(path24, checkUnignored, mode) {
499
499
  let ignored = false;
500
500
  let unignored = false;
501
501
  let matchedRule;
@@ -504,7 +504,7 @@ var require_ignore = __commonJS({
504
504
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
505
505
  return;
506
506
  }
507
- const matched = rule[mode].test(path23);
507
+ const matched = rule[mode].test(path24);
508
508
  if (!matched) {
509
509
  return;
510
510
  }
@@ -525,17 +525,17 @@ var require_ignore = __commonJS({
525
525
  var throwError = (message, Ctor) => {
526
526
  throw new Ctor(message);
527
527
  };
528
- var checkPath = (path23, originalPath, doThrow) => {
529
- if (!isString(path23)) {
528
+ var checkPath = (path24, originalPath, doThrow) => {
529
+ if (!isString(path24)) {
530
530
  return doThrow(
531
531
  `path must be a string, but got \`${originalPath}\``,
532
532
  TypeError
533
533
  );
534
534
  }
535
- if (!path23) {
535
+ if (!path24) {
536
536
  return doThrow(`path must not be empty`, TypeError);
537
537
  }
538
- if (checkPath.isNotRelative(path23)) {
538
+ if (checkPath.isNotRelative(path24)) {
539
539
  const r = "`path.relative()`d";
540
540
  return doThrow(
541
541
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -544,7 +544,7 @@ var require_ignore = __commonJS({
544
544
  }
545
545
  return true;
546
546
  };
547
- var isNotRelative = (path23) => REGEX_TEST_INVALID_PATH.test(path23);
547
+ var isNotRelative = (path24) => REGEX_TEST_INVALID_PATH.test(path24);
548
548
  checkPath.isNotRelative = isNotRelative;
549
549
  checkPath.convert = (p) => p;
550
550
  var Ignore2 = class {
@@ -574,19 +574,19 @@ var require_ignore = __commonJS({
574
574
  }
575
575
  // @returns {TestResult}
576
576
  _test(originalPath, cache, checkUnignored, slices) {
577
- const path23 = originalPath && checkPath.convert(originalPath);
577
+ const path24 = originalPath && checkPath.convert(originalPath);
578
578
  checkPath(
579
- path23,
579
+ path24,
580
580
  originalPath,
581
581
  this._strictPathCheck ? throwError : RETURN_FALSE
582
582
  );
583
- return this._t(path23, cache, checkUnignored, slices);
583
+ return this._t(path24, cache, checkUnignored, slices);
584
584
  }
585
- checkIgnore(path23) {
586
- if (!REGEX_TEST_TRAILING_SLASH.test(path23)) {
587
- return this.test(path23);
585
+ checkIgnore(path24) {
586
+ if (!REGEX_TEST_TRAILING_SLASH.test(path24)) {
587
+ return this.test(path24);
588
588
  }
589
- const slices = path23.split(SLASH2).filter(Boolean);
589
+ const slices = path24.split(SLASH2).filter(Boolean);
590
590
  slices.pop();
591
591
  if (slices.length) {
592
592
  const parent = this._t(
@@ -599,18 +599,18 @@ var require_ignore = __commonJS({
599
599
  return parent;
600
600
  }
601
601
  }
602
- return this._rules.test(path23, false, MODE_CHECK_IGNORE);
602
+ return this._rules.test(path24, false, MODE_CHECK_IGNORE);
603
603
  }
604
- _t(path23, cache, checkUnignored, slices) {
605
- if (path23 in cache) {
606
- return cache[path23];
604
+ _t(path24, cache, checkUnignored, slices) {
605
+ if (path24 in cache) {
606
+ return cache[path24];
607
607
  }
608
608
  if (!slices) {
609
- slices = path23.split(SLASH2).filter(Boolean);
609
+ slices = path24.split(SLASH2).filter(Boolean);
610
610
  }
611
611
  slices.pop();
612
612
  if (!slices.length) {
613
- return cache[path23] = this._rules.test(path23, checkUnignored, MODE_IGNORE);
613
+ return cache[path24] = this._rules.test(path24, checkUnignored, MODE_IGNORE);
614
614
  }
615
615
  const parent = this._t(
616
616
  slices.join(SLASH2) + SLASH2,
@@ -618,29 +618,29 @@ var require_ignore = __commonJS({
618
618
  checkUnignored,
619
619
  slices
620
620
  );
621
- return cache[path23] = parent.ignored ? parent : this._rules.test(path23, checkUnignored, MODE_IGNORE);
621
+ return cache[path24] = parent.ignored ? parent : this._rules.test(path24, checkUnignored, MODE_IGNORE);
622
622
  }
623
- ignores(path23) {
624
- return this._test(path23, this._ignoreCache, false).ignored;
623
+ ignores(path24) {
624
+ return this._test(path24, this._ignoreCache, false).ignored;
625
625
  }
626
626
  createFilter() {
627
- return (path23) => !this.ignores(path23);
627
+ return (path24) => !this.ignores(path24);
628
628
  }
629
629
  filter(paths) {
630
630
  return makeArray(paths).filter(this.createFilter());
631
631
  }
632
632
  // @returns {TestResult}
633
- test(path23) {
634
- return this._test(path23, this._testCache, true);
633
+ test(path24) {
634
+ return this._test(path24, this._testCache, true);
635
635
  }
636
636
  };
637
637
  var factory = (options) => new Ignore2(options);
638
- var isPathValid = (path23) => checkPath(path23 && checkPath.convert(path23), path23, RETURN_FALSE);
638
+ var isPathValid = (path24) => checkPath(path24 && checkPath.convert(path24), path24, RETURN_FALSE);
639
639
  var setupWindows = () => {
640
640
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
641
641
  checkPath.convert = makePosix;
642
642
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
643
- checkPath.isNotRelative = (path23) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path23) || isNotRelative(path23);
643
+ checkPath.isNotRelative = (path24) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path24) || isNotRelative(path24);
644
644
  };
645
645
  if (
646
646
  // Detect `process` so that it can run in browsers.
@@ -661,16 +661,16 @@ __export(index_exports, {
661
661
  default: () => index_default
662
662
  });
663
663
  module.exports = __toCommonJS(index_exports);
664
- var os5 = __toESM(require("os"), 1);
665
- var path22 = __toESM(require("path"), 1);
664
+ var os6 = __toESM(require("os"), 1);
665
+ var path23 = __toESM(require("path"), 1);
666
666
  var import_url2 = require("url");
667
667
 
668
668
  // src/config/constants.ts
669
669
  var DEFAULT_INCLUDE = [
670
- "**/*.{ts,tsx,js,jsx,mjs,cjs}",
670
+ "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
671
671
  "**/*.{py,pyi}",
672
- "**/*.{go,rs,java,kt,scala}",
673
- "**/*.{c,cpp,cc,h,hpp}",
672
+ "**/*.{go,rs,java,cs,kt,scala}",
673
+ "**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
674
674
  "**/*.{rb,php,inc,swift}",
675
675
  "**/*.{cls,trigger}",
676
676
  "**/*.{vue,svelte,astro}",
@@ -1254,6 +1254,9 @@ function getHostProjectIndexRelativePath(host) {
1254
1254
  function hasHostProjectConfig(projectRoot, host) {
1255
1255
  return (0, import_fs3.existsSync)(path3.join(projectRoot, getProjectConfigRelativePath(host)));
1256
1256
  }
1257
+ function hasHostGlobalConfig(host) {
1258
+ return (0, import_fs3.existsSync)(getGlobalConfigPath(host));
1259
+ }
1257
1260
  function getGlobalIndexPath(host = "opencode") {
1258
1261
  switch (host) {
1259
1262
  case "opencode":
@@ -1293,6 +1296,9 @@ function resolveGlobalIndexPath(host = "opencode") {
1293
1296
  return hostIndexPath;
1294
1297
  }
1295
1298
  if (host !== "opencode") {
1299
+ if (hasHostGlobalConfig(host)) {
1300
+ return hostIndexPath;
1301
+ }
1296
1302
  const legacyIndexPath = getGlobalIndexPath("opencode");
1297
1303
  if ((0, import_fs3.existsSync)(legacyIndexPath)) {
1298
1304
  return legacyIndexPath;
@@ -1591,13 +1597,429 @@ function loadMergedConfig(projectRoot, host = "opencode") {
1591
1597
  return merged;
1592
1598
  }
1593
1599
 
1600
+ // src/indexer/index-lock.ts
1601
+ var import_crypto = require("crypto");
1602
+ var import_fs5 = require("fs");
1603
+ var os2 = __toESM(require("os"), 1);
1604
+ var path7 = __toESM(require("path"), 1);
1605
+ var OWNER_FILE_NAME = "owner.json";
1606
+ var RECLAIM_DIRECTORY_NAME = "reclaiming";
1607
+ var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
1608
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1609
+ var VALID_OPERATIONS = /* @__PURE__ */ new Set([
1610
+ "initialize",
1611
+ "index",
1612
+ "force-index",
1613
+ "clear",
1614
+ "health-check",
1615
+ "retry-failed-batches",
1616
+ "recovery"
1617
+ ]);
1618
+ var temporaryCounter = 0;
1619
+ function getErrorCode(error) {
1620
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
1621
+ }
1622
+ function retryTransientFilesystemOperation(operation) {
1623
+ let lastError;
1624
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1625
+ try {
1626
+ operation();
1627
+ return;
1628
+ } catch (error) {
1629
+ lastError = error;
1630
+ const code = getErrorCode(error);
1631
+ if (code !== "EBUSY" && code !== "EPERM") throw error;
1632
+ if (attempt < 2) {
1633
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
1634
+ }
1635
+ }
1636
+ }
1637
+ throw lastError;
1638
+ }
1639
+ function parseOwner(value) {
1640
+ if (typeof value !== "object" || value === null) return null;
1641
+ const candidate = value;
1642
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
1643
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
1644
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
1645
+ if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
1646
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
1647
+ return candidate;
1648
+ }
1649
+ function parseReclaimOwner(value) {
1650
+ if (typeof value !== "object" || value === null) return null;
1651
+ const candidate = value;
1652
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
1653
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
1654
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
1655
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
1656
+ if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
1657
+ return candidate;
1658
+ }
1659
+ function readJsonDirectory(directoryPath, parser) {
1660
+ try {
1661
+ return parser(JSON.parse((0, import_fs5.readFileSync)(path7.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
1662
+ } catch {
1663
+ return null;
1664
+ }
1665
+ }
1666
+ function readDirectoryOwner(lockPath) {
1667
+ return readJsonDirectory(lockPath, parseOwner);
1668
+ }
1669
+ function readReclaimOwner(markerPath) {
1670
+ return readJsonDirectory(markerPath, parseReclaimOwner);
1671
+ }
1672
+ function readRecoveryOwner(markerPath) {
1673
+ return readDirectoryOwner(markerPath);
1674
+ }
1675
+ function readLegacyOwner(lockPath) {
1676
+ try {
1677
+ const parsed = JSON.parse((0, import_fs5.readFileSync)(lockPath, "utf-8"));
1678
+ if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
1679
+ if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
1680
+ return {
1681
+ pid: Number(parsed.pid),
1682
+ hostname: typeof parsed.hostname === "string" ? parsed.hostname : os2.hostname(),
1683
+ startedAt: parsed.startedAt,
1684
+ operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
1685
+ token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
1686
+ };
1687
+ } catch {
1688
+ return null;
1689
+ }
1690
+ }
1691
+ function getOwnerLiveness(owner) {
1692
+ if (owner.hostname !== os2.hostname()) return "unknown";
1693
+ try {
1694
+ process.kill(owner.pid, 0);
1695
+ return "alive";
1696
+ } catch (error) {
1697
+ const code = getErrorCode(error);
1698
+ if (code === "ESRCH") return "dead";
1699
+ if (code === "EPERM") return "alive";
1700
+ return "unknown";
1701
+ }
1702
+ }
1703
+ function sameOwner(left, right) {
1704
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
1705
+ }
1706
+ function sameReclaimOwner(left, right) {
1707
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
1708
+ }
1709
+ function publishJsonDirectory(finalPath, value) {
1710
+ const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
1711
+ (0, import_fs5.mkdirSync)(candidatePath, { mode: 448 });
1712
+ try {
1713
+ (0, import_fs5.writeFileSync)(path7.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
1714
+ encoding: "utf-8",
1715
+ flag: "wx",
1716
+ mode: 384
1717
+ });
1718
+ if ((0, import_fs5.existsSync)(finalPath)) return false;
1719
+ try {
1720
+ (0, import_fs5.renameSync)(candidatePath, finalPath);
1721
+ return true;
1722
+ } catch (error) {
1723
+ if ((0, import_fs5.existsSync)(finalPath)) return false;
1724
+ throw error;
1725
+ }
1726
+ } finally {
1727
+ if ((0, import_fs5.existsSync)(candidatePath)) (0, import_fs5.rmSync)(candidatePath, { recursive: true, force: true });
1728
+ }
1729
+ }
1730
+ function createOwner(operation) {
1731
+ return {
1732
+ pid: process.pid,
1733
+ hostname: os2.hostname(),
1734
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1735
+ operation,
1736
+ token: (0, import_crypto.randomUUID)()
1737
+ };
1738
+ }
1739
+ function recoveryMarkerPath(indexPath, owner) {
1740
+ return path7.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
1741
+ }
1742
+ function publishRecoveryMarker(indexPath, owner) {
1743
+ const markerPath = recoveryMarkerPath(indexPath, owner);
1744
+ if (!publishJsonDirectory(markerPath, owner)) {
1745
+ const markerOwner = readRecoveryOwner(markerPath);
1746
+ if (!markerOwner || !sameOwner(markerOwner, owner)) {
1747
+ throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
1748
+ }
1749
+ }
1750
+ return markerPath;
1751
+ }
1752
+ function getPendingRecoveries(indexPath) {
1753
+ const recoveries = [];
1754
+ const markerNames = (0, import_fs5.readdirSync)(indexPath).filter((name) => {
1755
+ if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
1756
+ return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
1757
+ }).sort();
1758
+ for (const markerName of markerNames) {
1759
+ const markerPath = path7.join(indexPath, markerName);
1760
+ const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
1761
+ let markerStats;
1762
+ try {
1763
+ markerStats = (0, import_fs5.lstatSync)(markerPath);
1764
+ } catch (error) {
1765
+ if (getErrorCode(error) === "ENOENT") continue;
1766
+ throw error;
1767
+ }
1768
+ if (!markerStats.isDirectory()) {
1769
+ throw new IndexLockContentionError(markerPath, null, "unknown-owner");
1770
+ }
1771
+ const owner = readRecoveryOwner(markerPath);
1772
+ if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
1773
+ throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
1774
+ }
1775
+ recoveries.push({ owner, markerPath });
1776
+ }
1777
+ recoveries.sort((left, right) => {
1778
+ const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
1779
+ return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
1780
+ });
1781
+ return recoveries;
1782
+ }
1783
+ function cleanupDeadPublicationCandidates(indexPath) {
1784
+ const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
1785
+ for (const entry of (0, import_fs5.readdirSync)(indexPath)) {
1786
+ const match = candidatePattern.exec(entry);
1787
+ if (!match) continue;
1788
+ const pid = Number(match[1]);
1789
+ if (!Number.isInteger(pid) || pid <= 0) continue;
1790
+ if (getOwnerLiveness({ pid, hostname: os2.hostname() }) !== "dead") continue;
1791
+ (0, import_fs5.rmSync)(path7.join(indexPath, entry), { recursive: true, force: true });
1792
+ }
1793
+ }
1794
+ function removeDeadReclaimMarker(lockPath, expectedOwner) {
1795
+ const markerPath = path7.join(lockPath, RECLAIM_DIRECTORY_NAME);
1796
+ const marker = readReclaimOwner(markerPath);
1797
+ if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
1798
+ return false;
1799
+ }
1800
+ const currentOwner = readDirectoryOwner(lockPath);
1801
+ if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
1802
+ return false;
1803
+ }
1804
+ const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
1805
+ try {
1806
+ (0, import_fs5.renameSync)(markerPath, claimedMarkerPath);
1807
+ } catch (error) {
1808
+ if (getErrorCode(error) === "ENOENT") return false;
1809
+ throw error;
1810
+ }
1811
+ const claimedMarker = readReclaimOwner(claimedMarkerPath);
1812
+ const ownerAfterClaim = readDirectoryOwner(lockPath);
1813
+ if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
1814
+ if (!(0, import_fs5.existsSync)(markerPath) && (0, import_fs5.existsSync)(claimedMarkerPath)) {
1815
+ (0, import_fs5.renameSync)(claimedMarkerPath, markerPath);
1816
+ }
1817
+ return false;
1818
+ }
1819
+ (0, import_fs5.rmSync)(claimedMarkerPath, { recursive: true, force: true });
1820
+ return true;
1821
+ }
1822
+ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
1823
+ const reclaimPath = path7.join(lockPath, RECLAIM_DIRECTORY_NAME);
1824
+ const reclaimOwner = {
1825
+ pid: process.pid,
1826
+ hostname: os2.hostname(),
1827
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1828
+ token: (0, import_crypto.randomUUID)(),
1829
+ expectedOwnerToken: expectedOwner.token
1830
+ };
1831
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1832
+ if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
1833
+ if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
1834
+ return false;
1835
+ }
1836
+ try {
1837
+ const currentReclaimer = readReclaimOwner(reclaimPath);
1838
+ const currentOwner = readDirectoryOwner(lockPath);
1839
+ if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
1840
+ return false;
1841
+ }
1842
+ publishRecoveryMarker(indexPath, expectedOwner);
1843
+ const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
1844
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
1845
+ if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
1846
+ return false;
1847
+ }
1848
+ const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
1849
+ (0, import_fs5.renameSync)(lockPath, quarantinePath);
1850
+ (0, import_fs5.rmSync)(quarantinePath, { recursive: true, force: true });
1851
+ return true;
1852
+ } catch (error) {
1853
+ if (getErrorCode(error) === "ENOENT") return false;
1854
+ throw error;
1855
+ }
1856
+ }
1857
+ var IndexLockContentionError = class extends Error {
1858
+ constructor(lockPath, owner, reason) {
1859
+ const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
1860
+ super(`Index mutation already in progress: ${ownerDescription}`);
1861
+ this.lockPath = lockPath;
1862
+ this.owner = owner;
1863
+ this.reason = reason;
1864
+ this.name = "IndexLockContentionError";
1865
+ }
1866
+ lockPath;
1867
+ owner;
1868
+ reason;
1869
+ code = "INDEX_BUSY";
1870
+ };
1871
+ function isIndexLockContentionError(error) {
1872
+ return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
1873
+ }
1874
+ function isTransientIndexLockContention(error) {
1875
+ if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
1876
+ return error.reason === "active" || error.reason === "reclaiming";
1877
+ }
1878
+ function acquireIndexLock(indexPath, operation) {
1879
+ (0, import_fs5.mkdirSync)(indexPath, { recursive: true });
1880
+ const canonicalIndexPath = import_fs5.realpathSync.native(indexPath);
1881
+ const lockPath = path7.join(canonicalIndexPath, "indexing.lock");
1882
+ cleanupDeadPublicationCandidates(canonicalIndexPath);
1883
+ for (let attempt = 0; attempt < 6; attempt += 1) {
1884
+ const owner = createOwner(operation);
1885
+ if (publishJsonDirectory(lockPath, owner)) {
1886
+ const lease = {
1887
+ canonicalIndexPath,
1888
+ lockPath,
1889
+ owner,
1890
+ recoveries: []
1891
+ };
1892
+ try {
1893
+ lease.recoveries = getPendingRecoveries(canonicalIndexPath);
1894
+ return lease;
1895
+ } catch (error) {
1896
+ releaseIndexLock(lease);
1897
+ throw error;
1898
+ }
1899
+ }
1900
+ let stats;
1901
+ try {
1902
+ stats = (0, import_fs5.lstatSync)(lockPath);
1903
+ } catch (error) {
1904
+ if (getErrorCode(error) === "ENOENT") continue;
1905
+ throw error;
1906
+ }
1907
+ if (!stats.isDirectory()) {
1908
+ const legacyOwner = readLegacyOwner(lockPath);
1909
+ throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
1910
+ }
1911
+ const existingOwner = readDirectoryOwner(lockPath);
1912
+ if (!existingOwner) {
1913
+ throw new IndexLockContentionError(lockPath, null, "unknown-owner");
1914
+ }
1915
+ const liveness = getOwnerLiveness(existingOwner);
1916
+ if (liveness !== "dead") {
1917
+ throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
1918
+ }
1919
+ if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
1920
+ throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
1921
+ }
1922
+ }
1923
+ throw new IndexLockContentionError(lockPath, null, "reclaiming");
1924
+ }
1925
+ function releaseIndexLock(lease) {
1926
+ const currentOwner = readDirectoryOwner(lease.lockPath);
1927
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
1928
+ const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
1929
+ try {
1930
+ retryTransientFilesystemOperation(() => (0, import_fs5.renameSync)(lease.lockPath, releasePath));
1931
+ } catch (error) {
1932
+ if (getErrorCode(error) === "ENOENT") return false;
1933
+ throw error;
1934
+ }
1935
+ const claimedOwner = readDirectoryOwner(releasePath);
1936
+ if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
1937
+ if (!(0, import_fs5.existsSync)(lease.lockPath) && (0, import_fs5.existsSync)(releasePath)) {
1938
+ (0, import_fs5.renameSync)(releasePath, lease.lockPath);
1939
+ }
1940
+ return false;
1941
+ }
1942
+ try {
1943
+ retryTransientFilesystemOperation(() => (0, import_fs5.rmSync)(releasePath, { recursive: true, force: true }));
1944
+ } catch (error) {
1945
+ console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
1946
+ }
1947
+ return true;
1948
+ }
1949
+ async function withIndexLock(indexPath, operation, callback, options = {}) {
1950
+ const lease = acquireIndexLock(indexPath, operation);
1951
+ let result;
1952
+ let callbackError;
1953
+ let callbackFailed = false;
1954
+ try {
1955
+ result = await callback(lease);
1956
+ } catch (error) {
1957
+ callbackFailed = true;
1958
+ callbackError = error;
1959
+ }
1960
+ if (!callbackFailed && options.completeRecoveries !== false) {
1961
+ try {
1962
+ completeLeaseRecovery(lease);
1963
+ } catch (error) {
1964
+ callbackFailed = true;
1965
+ callbackError = error;
1966
+ }
1967
+ }
1968
+ let releaseError;
1969
+ try {
1970
+ if (!releaseIndexLock(lease)) {
1971
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
1972
+ }
1973
+ } catch (error) {
1974
+ releaseError = error;
1975
+ }
1976
+ if (releaseError !== void 0) {
1977
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
1978
+ throw releaseError;
1979
+ }
1980
+ if (callbackFailed) throw callbackError;
1981
+ return result;
1982
+ }
1983
+ function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
1984
+ if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
1985
+ temporaryCounter += 1;
1986
+ return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
1987
+ }
1988
+ function removeLeaseTemporaryPath(temporaryPath) {
1989
+ if ((0, import_fs5.existsSync)(temporaryPath)) (0, import_fs5.rmSync)(temporaryPath, { recursive: true, force: true });
1990
+ }
1991
+ function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
1992
+ for (const targetPath of backupTargets) {
1993
+ const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
1994
+ if (!(0, import_fs5.existsSync)(backupPath)) continue;
1995
+ if ((0, import_fs5.existsSync)(targetPath)) (0, import_fs5.rmSync)(targetPath, { recursive: true, force: true });
1996
+ (0, import_fs5.renameSync)(backupPath, targetPath);
1997
+ }
1998
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
1999
+ for (const entry of (0, import_fs5.readdirSync)(indexPath)) {
2000
+ if (!entry.includes(temporaryOwnerMarker)) continue;
2001
+ (0, import_fs5.rmSync)(path7.join(indexPath, entry), { recursive: true, force: true });
2002
+ }
2003
+ }
2004
+ function completeLeaseRecovery(lease) {
2005
+ for (const recovery of lease.recoveries) {
2006
+ const markerOwner = readRecoveryOwner(recovery.markerPath);
2007
+ if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
2008
+ throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
2009
+ }
2010
+ }
2011
+ for (const recovery of lease.recoveries) {
2012
+ (0, import_fs5.rmSync)(recovery.markerPath, { recursive: true, force: true });
2013
+ }
2014
+ }
2015
+
1594
2016
  // src/tools/operations.ts
1595
- var import_fs9 = require("fs");
1596
- var path15 = __toESM(require("path"), 1);
2017
+ var import_fs10 = require("fs");
2018
+ var path16 = __toESM(require("path"), 1);
1597
2019
 
1598
2020
  // src/indexer/index.ts
1599
- var import_fs7 = require("fs");
1600
- var path12 = __toESM(require("path"), 1);
2021
+ var import_fs8 = require("fs");
2022
+ var path13 = __toESM(require("path"), 1);
1601
2023
  var import_perf_hooks = require("perf_hooks");
1602
2024
  var import_child_process3 = require("child_process");
1603
2025
  var import_util3 = require("util");
@@ -2647,17 +3069,17 @@ async function pRetry(input, options = {}) {
2647
3069
  }
2648
3070
 
2649
3071
  // src/embeddings/detector.ts
2650
- var import_fs5 = require("fs");
2651
- var path7 = __toESM(require("path"), 1);
2652
- var os2 = __toESM(require("os"), 1);
3072
+ var import_fs6 = require("fs");
3073
+ var path8 = __toESM(require("path"), 1);
3074
+ var os3 = __toESM(require("os"), 1);
2653
3075
  function getOpenCodeAuthPath() {
2654
- return path7.join(os2.homedir(), ".local", "share", "opencode", "auth.json");
3076
+ return path8.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
2655
3077
  }
2656
3078
  function loadOpenCodeAuth() {
2657
3079
  const authPath = getOpenCodeAuthPath();
2658
3080
  try {
2659
- if ((0, import_fs5.existsSync)(authPath)) {
2660
- return JSON.parse((0, import_fs5.readFileSync)(authPath, "utf-8"));
3081
+ if ((0, import_fs6.existsSync)(authPath)) {
3082
+ return JSON.parse((0, import_fs6.readFileSync)(authPath, "utf-8"));
2661
3083
  }
2662
3084
  } catch {
2663
3085
  }
@@ -2879,17 +3301,17 @@ function validateExternalUrl(urlString) {
2879
3301
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2880
3302
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2881
3303
  }
2882
- const hostname = parsed.hostname.toLowerCase();
2883
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2884
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
3304
+ const hostname2 = parsed.hostname.toLowerCase();
3305
+ if (BLOCKED_HOSTNAMES.has(hostname2)) {
3306
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
2885
3307
  }
2886
3308
  for (const pattern of BLOCKED_METADATA_IPS) {
2887
- if (pattern.test(hostname)) {
2888
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
3309
+ if (pattern.test(hostname2)) {
3310
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2889
3311
  }
2890
3312
  }
2891
- if (/^169\.254\./.test(hostname)) {
2892
- return { valid: false, reason: `Blocked: link-local address (${hostname})` };
3313
+ if (/^169\.254\./.test(hostname2)) {
3314
+ return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
2893
3315
  }
2894
3316
  return { valid: true };
2895
3317
  }
@@ -3360,8 +3782,8 @@ var SiliconFlowReranker = class {
3360
3782
 
3361
3783
  // src/utils/files.ts
3362
3784
  var import_ignore = __toESM(require_ignore(), 1);
3363
- var import_fs6 = require("fs");
3364
- var path8 = __toESM(require("path"), 1);
3785
+ var import_fs7 = require("fs");
3786
+ var path9 = __toESM(require("path"), 1);
3365
3787
  var PROJECT_MARKERS = [
3366
3788
  ".git",
3367
3789
  "package.json",
@@ -3381,7 +3803,7 @@ var PROJECT_MARKERS = [
3381
3803
  ];
3382
3804
  function hasProjectMarker(projectRoot) {
3383
3805
  for (const marker of PROJECT_MARKERS) {
3384
- if ((0, import_fs6.existsSync)(path8.join(projectRoot, marker))) {
3806
+ if ((0, import_fs7.existsSync)(path9.join(projectRoot, marker))) {
3385
3807
  return true;
3386
3808
  }
3387
3809
  }
@@ -3408,16 +3830,16 @@ function createIgnoreFilter(projectRoot) {
3408
3830
  "**/*build*/**"
3409
3831
  ];
3410
3832
  ig.add(defaultIgnores);
3411
- const gitignorePath = path8.join(projectRoot, ".gitignore");
3412
- if ((0, import_fs6.existsSync)(gitignorePath)) {
3413
- const gitignoreContent = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
3833
+ const gitignorePath = path9.join(projectRoot, ".gitignore");
3834
+ if ((0, import_fs7.existsSync)(gitignorePath)) {
3835
+ const gitignoreContent = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
3414
3836
  ig.add(gitignoreContent);
3415
3837
  }
3416
3838
  return ig;
3417
3839
  }
3418
3840
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3419
- const relativePath = path8.relative(projectRoot, filePath);
3420
- if (hasFilteredPathSegment(relativePath, path8.sep)) {
3841
+ const relativePath = path9.relative(projectRoot, filePath);
3842
+ if (hasFilteredPathSegment(relativePath, path9.sep)) {
3421
3843
  return false;
3422
3844
  }
3423
3845
  if (ignoreFilter.ignores(relativePath)) {
@@ -3451,12 +3873,12 @@ function matchGlob(filePath, pattern) {
3451
3873
  return regex.test(filePath);
3452
3874
  }
3453
3875
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3454
- const entries = await import_fs6.promises.readdir(dir, { withFileTypes: true });
3876
+ const entries = await import_fs7.promises.readdir(dir, { withFileTypes: true });
3455
3877
  const filesInDir = [];
3456
3878
  const subdirs = [];
3457
3879
  for (const entry of entries) {
3458
- const fullPath = path8.join(dir, entry.name);
3459
- const relativePath = path8.relative(projectRoot, fullPath);
3880
+ const fullPath = path9.join(dir, entry.name);
3881
+ const relativePath = path9.relative(projectRoot, fullPath);
3460
3882
  if (isHiddenPathSegment(entry.name)) {
3461
3883
  if (entry.isDirectory()) {
3462
3884
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3476,7 +3898,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3476
3898
  if (entry.isDirectory()) {
3477
3899
  subdirs.push({ fullPath, relativePath });
3478
3900
  } else if (entry.isFile()) {
3479
- const stat4 = await import_fs6.promises.stat(fullPath);
3901
+ const stat4 = await import_fs7.promises.stat(fullPath);
3480
3902
  if (stat4.size > maxFileSize) {
3481
3903
  skipped.push({ path: relativePath, reason: "too_large" });
3482
3904
  continue;
@@ -3505,7 +3927,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3505
3927
  yield f;
3506
3928
  }
3507
3929
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3508
- skipped.push({ path: path8.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3930
+ skipped.push({ path: path9.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3509
3931
  }
3510
3932
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3511
3933
  if (canRecurse) {
@@ -3545,14 +3967,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3545
3967
  if (additionalRoots && additionalRoots.length > 0) {
3546
3968
  const normalizedRoots = /* @__PURE__ */ new Set();
3547
3969
  for (const kbRoot of additionalRoots) {
3548
- const resolved = path8.normalize(
3549
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot, kbRoot)
3970
+ const resolved = path9.normalize(
3971
+ path9.isAbsolute(kbRoot) ? kbRoot : path9.resolve(projectRoot, kbRoot)
3550
3972
  );
3551
3973
  normalizedRoots.add(resolved);
3552
3974
  }
3553
3975
  for (const resolvedKbRoot of normalizedRoots) {
3554
3976
  try {
3555
- const stat4 = await import_fs6.promises.stat(resolvedKbRoot);
3977
+ const stat4 = await import_fs7.promises.stat(resolvedKbRoot);
3556
3978
  if (!stat4.isDirectory()) {
3557
3979
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3558
3980
  continue;
@@ -3953,14 +4375,14 @@ function initializeLogger(config) {
3953
4375
  }
3954
4376
 
3955
4377
  // src/native/index.ts
3956
- var path9 = __toESM(require("path"), 1);
3957
- var os3 = __toESM(require("os"), 1);
4378
+ var path10 = __toESM(require("path"), 1);
4379
+ var os4 = __toESM(require("os"), 1);
3958
4380
  var module2 = __toESM(require("module"), 1);
3959
4381
  var import_url = require("url");
3960
4382
  var import_meta = {};
3961
4383
  function getNativeBinding() {
3962
- const platform2 = os3.platform();
3963
- const arch2 = os3.arch();
4384
+ const platform2 = os4.platform();
4385
+ const arch2 = os4.arch();
3964
4386
  let bindingName;
3965
4387
  if (platform2 === "darwin" && arch2 === "arm64") {
3966
4388
  bindingName = "codebase-index-native.darwin-arm64.node";
@@ -3978,19 +4400,19 @@ function getNativeBinding() {
3978
4400
  let currentDir;
3979
4401
  let requireTarget;
3980
4402
  if (typeof import_meta !== "undefined" && import_meta.url) {
3981
- currentDir = path9.dirname((0, import_url.fileURLToPath)(import_meta.url));
4403
+ currentDir = path10.dirname((0, import_url.fileURLToPath)(import_meta.url));
3982
4404
  requireTarget = import_meta.url;
3983
4405
  } else if (typeof __dirname !== "undefined") {
3984
4406
  currentDir = __dirname;
3985
4407
  requireTarget = __filename;
3986
4408
  } else {
3987
4409
  currentDir = process.cwd();
3988
- requireTarget = path9.join(currentDir, "index.js");
4410
+ requireTarget = path10.join(currentDir, "index.js");
3989
4411
  }
3990
4412
  const normalizedDir = currentDir.replace(/\\/g, "/");
3991
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path9.join("src", "native"));
3992
- const packageRoot = isDevMode ? path9.resolve(currentDir, "../..") : path9.resolve(currentDir, "..");
3993
- const nativePath = path9.join(packageRoot, "native", bindingName);
4413
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path10.join("src", "native"));
4414
+ const packageRoot = isDevMode ? path10.resolve(currentDir, "../..") : path10.resolve(currentDir, "..");
4415
+ const nativePath = path10.join(packageRoot, "native", bindingName);
3994
4416
  const require2 = module2.createRequire(requireTarget);
3995
4417
  return require2(nativePath);
3996
4418
  }
@@ -4032,6 +4454,12 @@ function createMockNativeBinding() {
4032
4454
  constructor() {
4033
4455
  throw error;
4034
4456
  }
4457
+ static openReadOnly() {
4458
+ throw error;
4459
+ }
4460
+ static createEmptyReadOnly() {
4461
+ throw error;
4462
+ }
4035
4463
  close() {
4036
4464
  throw error;
4037
4465
  }
@@ -4135,6 +4563,12 @@ var VectorStore = class {
4135
4563
  load() {
4136
4564
  this.inner.load();
4137
4565
  }
4566
+ loadStrict() {
4567
+ this.inner.loadStrict();
4568
+ }
4569
+ hasFingerprint() {
4570
+ return this.inner.hasFingerprint();
4571
+ }
4138
4572
  count() {
4139
4573
  return this.inner.count();
4140
4574
  }
@@ -4417,12 +4851,24 @@ var InvertedIndex = class {
4417
4851
  return this.inner.documentCount();
4418
4852
  }
4419
4853
  };
4420
- var Database = class {
4854
+ var Database = class _Database {
4421
4855
  inner;
4422
4856
  closed = false;
4423
4857
  constructor(dbPath) {
4424
4858
  this.inner = new native.Database(dbPath);
4425
4859
  }
4860
+ static fromNative(inner) {
4861
+ const database = Object.create(_Database.prototype);
4862
+ database.inner = inner;
4863
+ database.closed = false;
4864
+ return database;
4865
+ }
4866
+ static openReadOnly(dbPath) {
4867
+ return _Database.fromNative(native.Database.openReadOnly(dbPath));
4868
+ }
4869
+ static createEmptyReadOnly() {
4870
+ return _Database.fromNative(native.Database.createEmptyReadOnly());
4871
+ }
4426
4872
  throwIfClosed() {
4427
4873
  if (this.closed) {
4428
4874
  throw new Error("Database is closed");
@@ -4699,7 +5145,7 @@ var Database = class {
4699
5145
  };
4700
5146
 
4701
5147
  // src/tools/changed-files.ts
4702
- var path10 = __toESM(require("path"), 1);
5148
+ var path11 = __toESM(require("path"), 1);
4703
5149
  var import_child_process = require("child_process");
4704
5150
  var import_util = require("util");
4705
5151
  var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
@@ -4787,8 +5233,8 @@ function normalizeFiles(rawFiles, projectRoot) {
4787
5233
  for (const raw of rawFiles) {
4788
5234
  const trimmed = raw.trim();
4789
5235
  if (!trimmed) continue;
4790
- const absolute = path10.resolve(projectRoot, trimmed);
4791
- const relative11 = path10.relative(projectRoot, absolute);
5236
+ const absolute = path11.resolve(projectRoot, trimmed);
5237
+ const relative11 = path11.relative(projectRoot, absolute);
4792
5238
  const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
4793
5239
  if (!seen.has(cleaned)) {
4794
5240
  seen.add(cleaned);
@@ -4800,7 +5246,7 @@ function normalizeFiles(rawFiles, projectRoot) {
4800
5246
 
4801
5247
  // src/indexer/git-blame.ts
4802
5248
  var import_child_process2 = require("child_process");
4803
- var path11 = __toESM(require("path"), 1);
5249
+ var path12 = __toESM(require("path"), 1);
4804
5250
  var import_util2 = require("util");
4805
5251
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
4806
5252
  function parseGitBlamePorcelain(output) {
@@ -4838,7 +5284,7 @@ function parseGitBlamePorcelain(output) {
4838
5284
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4839
5285
  }
4840
5286
  async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
4841
- const relativePath = path11.relative(projectRoot, filePath);
5287
+ const relativePath = path12.relative(projectRoot, filePath);
4842
5288
  try {
4843
5289
  const { stdout } = await execFileAsync2(
4844
5290
  "git",
@@ -4932,6 +5378,7 @@ function isSqliteCorruptionError(error) {
4932
5378
  return message.includes("database disk image is malformed") || message.includes("file is not a database") || message.includes("database schema is corrupt") || message.includes("sqlite_corrupt");
4933
5379
  }
4934
5380
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5381
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
4935
5382
  function metadataFromBlame(blame) {
4936
5383
  if (!blame) {
4937
5384
  return {};
@@ -5129,9 +5576,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5129
5576
  return true;
5130
5577
  }
5131
5578
  function isPathWithinRoot(filePath, rootPath) {
5132
- const normalizedFilePath = path12.resolve(filePath);
5133
- const normalizedRoot = path12.resolve(rootPath);
5134
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
5579
+ const normalizedFilePath = path13.resolve(filePath);
5580
+ const normalizedRoot = path13.resolve(rootPath);
5581
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5135
5582
  }
5136
5583
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5137
5584
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -6197,26 +6644,133 @@ var Indexer = class {
6197
6644
  queryCacheTtlMs = 5 * 60 * 1e3;
6198
6645
  querySimilarityThreshold = 0.85;
6199
6646
  indexCompatibility = null;
6200
- indexingLockPath = "";
6647
+ activeIndexLease = null;
6648
+ initializationPromise = null;
6649
+ initializationMode = "none";
6650
+ readIssues = [];
6651
+ retiredDatabases = [];
6652
+ readerArtifactFingerprint = null;
6653
+ writerArtifactFingerprint = null;
6654
+ readerArtifactRetryAfter = /* @__PURE__ */ new Map();
6201
6655
  constructor(projectRoot, config, host = "opencode") {
6202
6656
  this.projectRoot = projectRoot;
6203
6657
  this.config = config;
6204
6658
  this.host = host;
6205
6659
  this.indexPath = this.getIndexPath();
6206
- this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6207
- this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6208
- this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
6660
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6661
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6209
6662
  this.logger = initializeLogger(config.debug);
6210
6663
  }
6211
6664
  getIndexPath() {
6212
6665
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6213
6666
  }
6667
+ isLocalProjectIndexPath() {
6668
+ const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6669
+ if (this.host !== "opencode") {
6670
+ localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6671
+ }
6672
+ return localProjectIndexPaths.some((localPath) => {
6673
+ if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
6674
+ return path13.resolve(this.indexPath) === path13.resolve(localPath);
6675
+ }
6676
+ const indexStats = (0, import_fs8.statSync)(this.indexPath);
6677
+ const localStats = (0, import_fs8.statSync)(localPath);
6678
+ return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6679
+ });
6680
+ }
6681
+ resetLoadedIndexState(retireDatabase = false) {
6682
+ if (this.database) {
6683
+ if (retireDatabase) {
6684
+ this.retiredDatabases.push(this.database);
6685
+ } else {
6686
+ this.database.close();
6687
+ }
6688
+ }
6689
+ this.store = null;
6690
+ this.invertedIndex = null;
6691
+ this.database = null;
6692
+ this.provider = null;
6693
+ this.configuredProviderInfo = null;
6694
+ this.reranker = null;
6695
+ this.indexCompatibility = null;
6696
+ this.initializationMode = "none";
6697
+ this.readIssues = [];
6698
+ this.readerArtifactFingerprint = null;
6699
+ this.writerArtifactFingerprint = null;
6700
+ this.readerArtifactRetryAfter.clear();
6701
+ this.fileHashCache.clear();
6702
+ }
6703
+ refreshLoadedIndexState() {
6704
+ if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
6705
+ this.store.load();
6706
+ this.invertedIndex.load();
6707
+ this.fileHashCache.clear();
6708
+ this.loadFileHashCache();
6709
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
6710
+ this.readIssues = [];
6711
+ this.readerArtifactRetryAfter.clear();
6712
+ }
6713
+ async withIndexMutationLease(operation, callback) {
6714
+ const lease = acquireIndexLock(this.indexPath, operation);
6715
+ this.indexPath = lease.canonicalIndexPath;
6716
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6717
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6718
+ this.activeIndexLease = lease;
6719
+ let result;
6720
+ let callbackError;
6721
+ let callbackFailed = false;
6722
+ try {
6723
+ result = await callback(lease.recoveries.map(({ owner }) => owner));
6724
+ } catch (error) {
6725
+ callbackFailed = true;
6726
+ callbackError = error;
6727
+ }
6728
+ if (!callbackFailed) {
6729
+ try {
6730
+ completeLeaseRecovery(lease);
6731
+ this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
6732
+ } catch (error) {
6733
+ callbackFailed = true;
6734
+ callbackError = error;
6735
+ }
6736
+ }
6737
+ let releaseError;
6738
+ try {
6739
+ if (!releaseIndexLock(lease)) {
6740
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
6741
+ this.writerArtifactFingerprint = null;
6742
+ if (this.activeIndexLease?.owner.token === lease.owner.token) {
6743
+ this.activeIndexLease = null;
6744
+ }
6745
+ } else if (this.activeIndexLease?.owner.token === lease.owner.token) {
6746
+ this.activeIndexLease = null;
6747
+ }
6748
+ } catch (error) {
6749
+ releaseError = error;
6750
+ this.writerArtifactFingerprint = null;
6751
+ if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6752
+ this.activeIndexLease = null;
6753
+ }
6754
+ }
6755
+ if (releaseError !== void 0) {
6756
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
6757
+ throw releaseError;
6758
+ }
6759
+ if (callbackFailed) throw callbackError;
6760
+ return result;
6761
+ }
6762
+ requireActiveLease() {
6763
+ if (!this.activeIndexLease) {
6764
+ throw new Error("Index mutation attempted without an active interprocess lease");
6765
+ }
6766
+ return this.activeIndexLease;
6767
+ }
6214
6768
  loadFileHashCache() {
6215
- if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
6769
+ if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
6216
6770
  return;
6217
6771
  }
6218
6772
  try {
6219
- const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
6773
+ const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
6220
6774
  const parsed = JSON.parse(data);
6221
6775
  this.fileHashCache = new Map(Object.entries(parsed));
6222
6776
  } catch (error) {
@@ -6236,15 +6790,26 @@ var Indexer = class {
6236
6790
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6237
6791
  }
6238
6792
  atomicWriteSync(targetPath, data) {
6239
- const tempPath = `${targetPath}.tmp`;
6240
- (0, import_fs7.mkdirSync)(path12.dirname(targetPath), { recursive: true });
6241
- (0, import_fs7.writeFileSync)(tempPath, data);
6242
- (0, import_fs7.renameSync)(tempPath, targetPath);
6793
+ const lease = this.requireActiveLease();
6794
+ const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6795
+ (0, import_fs8.mkdirSync)(path13.dirname(targetPath), { recursive: true });
6796
+ try {
6797
+ (0, import_fs8.writeFileSync)(tempPath, data);
6798
+ (0, import_fs8.renameSync)(tempPath, targetPath);
6799
+ } finally {
6800
+ removeLeaseTemporaryPath(tempPath);
6801
+ }
6802
+ }
6803
+ saveInvertedIndex(invertedIndex) {
6804
+ this.atomicWriteSync(
6805
+ path13.join(this.indexPath, "inverted-index.json"),
6806
+ invertedIndex.serialize()
6807
+ );
6243
6808
  }
6244
6809
  getScopedRoots() {
6245
- const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
6810
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6246
6811
  for (const kbRoot of this.config.knowledgeBases) {
6247
- roots.add(path12.resolve(this.projectRoot, kbRoot));
6812
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
6248
6813
  }
6249
6814
  return Array.from(roots);
6250
6815
  }
@@ -6253,29 +6818,29 @@ var Indexer = class {
6253
6818
  if (this.config.scope !== "global") {
6254
6819
  return branchName;
6255
6820
  }
6256
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6821
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6257
6822
  return `${projectHash}:${branchName}`;
6258
6823
  }
6259
6824
  getBranchCatalogKeyFor(branchName) {
6260
6825
  if (this.config.scope !== "global") {
6261
6826
  return branchName;
6262
6827
  }
6263
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6828
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6264
6829
  return `${projectHash}:${branchName}`;
6265
6830
  }
6266
6831
  getLegacyBranchCatalogKey() {
6267
6832
  return this.currentBranch || "default";
6268
6833
  }
6269
6834
  getLegacyMigrationMetadataKey() {
6270
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6835
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6271
6836
  return `index.globalBranchMigration.${projectHash}`;
6272
6837
  }
6273
6838
  getProjectEmbeddingStrategyMetadataKey() {
6274
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6839
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6275
6840
  return `index.embeddingStrategyVersion.${projectHash}`;
6276
6841
  }
6277
6842
  getProjectForceReembedMetadataKey() {
6278
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6843
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6279
6844
  return `index.forceReembed.${projectHash}`;
6280
6845
  }
6281
6846
  hasProjectForceReembedPending() {
@@ -6369,7 +6934,7 @@ var Indexer = class {
6369
6934
  if (!this.database) {
6370
6935
  return { chunkIds, symbolIds };
6371
6936
  }
6372
- const projectRootPath = path12.resolve(this.projectRoot);
6937
+ const projectRootPath = path13.resolve(this.projectRoot);
6373
6938
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6374
6939
  ...Array.from(this.fileHashCache.keys()).filter(
6375
6940
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6392,7 +6957,7 @@ var Indexer = class {
6392
6957
  if (this.config.scope !== "global") {
6393
6958
  return this.getBranchCatalogCleanupKeys();
6394
6959
  }
6395
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6960
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6396
6961
  const keys = /* @__PURE__ */ new Set();
6397
6962
  const projectChunkIdSet = new Set(projectChunkIds);
6398
6963
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6476,7 +7041,7 @@ var Indexer = class {
6476
7041
  if (!this.database || this.config.scope !== "global") {
6477
7042
  return false;
6478
7043
  }
6479
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7044
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6480
7045
  const roots = this.getScopedRoots();
6481
7046
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6482
7047
  return this.database.getAllBranches().some(
@@ -6510,7 +7075,7 @@ var Indexer = class {
6510
7075
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6511
7076
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6512
7077
  ]);
6513
- const projectRootPath = path12.resolve(this.projectRoot);
7078
+ const projectRootPath = path13.resolve(this.projectRoot);
6514
7079
  const projectLocalFilePaths = new Set(
6515
7080
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6516
7081
  );
@@ -6572,34 +7137,27 @@ var Indexer = class {
6572
7137
  database.gcOrphanEmbeddings();
6573
7138
  database.gcOrphanChunks();
6574
7139
  store.save();
6575
- invertedIndex.save();
7140
+ this.saveInvertedIndex(invertedIndex);
6576
7141
  return {
6577
7142
  removedChunkIds: removedChunkIdList,
6578
7143
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6579
7144
  };
6580
7145
  }
6581
- checkForInterruptedIndexing() {
6582
- return (0, import_fs7.existsSync)(this.indexingLockPath);
6583
- }
6584
- acquireIndexingLock() {
6585
- const lockData = {
6586
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6587
- pid: process.pid
6588
- };
6589
- (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
6590
- }
6591
- releaseIndexingLock() {
6592
- if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
6593
- (0, import_fs7.unlinkSync)(this.indexingLockPath);
7146
+ async recoverFromInterruptedIndexingUnlocked(owners) {
7147
+ for (const owner of owners) {
7148
+ this.logger.warn("Detected interrupted indexing session, recovering...", {
7149
+ pid: owner.pid,
7150
+ hostname: owner.hostname,
7151
+ operation: owner.operation,
7152
+ startedAt: owner.startedAt
7153
+ });
6594
7154
  }
6595
- }
6596
- async recoverFromInterruptedIndexing() {
6597
- this.logger.warn("Detected interrupted indexing session, recovering...");
6598
- if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
6599
- (0, import_fs7.unlinkSync)(this.fileHashCachePath);
7155
+ if (this.config.scope === "global") {
7156
+ if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
7157
+ (0, import_fs8.unlinkSync)(this.fileHashCachePath);
7158
+ }
7159
+ await this.healthCheckUnlocked();
6600
7160
  }
6601
- await this.healthCheck();
6602
- this.releaseIndexingLock();
6603
7161
  this.logger.info("Recovery complete, next index will re-process all files");
6604
7162
  }
6605
7163
  loadFailedBatches(maxChunkTokens) {
@@ -6615,10 +7173,10 @@ var Indexer = class {
6615
7173
  }
6616
7174
  }
6617
7175
  loadSerializedFailedBatches() {
6618
- if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
7176
+ if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
6619
7177
  return [];
6620
7178
  }
6621
- const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
7179
+ const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
6622
7180
  const parsed = JSON.parse(data);
6623
7181
  return parsed.map((batch) => {
6624
7182
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6635,15 +7193,15 @@ var Indexer = class {
6635
7193
  }
6636
7194
  saveFailedBatches(batches) {
6637
7195
  if (batches.length === 0) {
6638
- if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
7196
+ if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
6639
7197
  try {
6640
- (0, import_fs7.unlinkSync)(this.failedBatchesPath);
7198
+ (0, import_fs8.unlinkSync)(this.failedBatchesPath);
6641
7199
  } catch {
6642
7200
  }
6643
7201
  }
6644
7202
  return;
6645
7203
  }
6646
- (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7204
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6647
7205
  }
6648
7206
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6649
7207
  const retryableById = /* @__PURE__ */ new Map();
@@ -6823,7 +7381,7 @@ var Indexer = class {
6823
7381
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
6824
7382
  parts.push(`intent_hint: ${intent}`);
6825
7383
  try {
6826
- const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
7384
+ const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
6827
7385
  const lines = fileContent.split("\n");
6828
7386
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
6829
7387
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -6837,6 +7395,222 @@ var Indexer = class {
6837
7395
  return parts.join("\n");
6838
7396
  }
6839
7397
  async initialize() {
7398
+ if (this.initializationPromise) {
7399
+ await this.initializationPromise;
7400
+ }
7401
+ if (this.isInitializedFor("reader")) {
7402
+ return;
7403
+ }
7404
+ await this.initializeOnce("reader", [], { skipAutoGc: true });
7405
+ }
7406
+ async initializeOnce(mode, recoveredOwners, options) {
7407
+ if (this.initializationPromise) {
7408
+ await this.initializationPromise;
7409
+ if (this.isInitializedFor(mode)) {
7410
+ return;
7411
+ }
7412
+ return this.initializeOnce(mode, recoveredOwners, options);
7413
+ }
7414
+ if (this.isInitializedFor(mode)) {
7415
+ return;
7416
+ }
7417
+ const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
7418
+ this.resetLoadedIndexState();
7419
+ throw error;
7420
+ }).finally(() => {
7421
+ if (this.initializationPromise === initialization) {
7422
+ this.initializationPromise = null;
7423
+ }
7424
+ });
7425
+ this.initializationPromise = initialization;
7426
+ await initialization;
7427
+ }
7428
+ isInitializedFor(mode) {
7429
+ const hasState = Boolean(
7430
+ this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
7431
+ );
7432
+ if (!hasState) {
7433
+ return false;
7434
+ }
7435
+ return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
7436
+ }
7437
+ recordReadIssue(component, message, error) {
7438
+ this.readIssues.push(this.createReadIssue(component, message));
7439
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7440
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7441
+ }
7442
+ createReadIssue(component, message) {
7443
+ return {
7444
+ component,
7445
+ message,
7446
+ blocking: component !== "keyword"
7447
+ };
7448
+ }
7449
+ getVectorReadIssueMessage() {
7450
+ if (this.config.scope === "global") {
7451
+ return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
7452
+ }
7453
+ if (!this.isLocalProjectIndexPath()) {
7454
+ return "Vector index could not be read from an inherited project index. Restore or fingerprint it from the checkout that owns the index; do not remove or rebuild it from this worktree.";
7455
+ }
7456
+ return "Vector index could not be read. Run index_codebase after the active writer finishes to fingerprint a structurally valid legacy pair, or remove this checkout's local index directory and run index_codebase to rebuild it.";
7457
+ }
7458
+ getKeywordReadIssueMessage() {
7459
+ if (this.config.scope === "global") {
7460
+ return "Shared keyword index could not be read; semantic search remains available. Restore or repair the shared keyword artifact; automatic reset is disabled for global scope.";
7461
+ }
7462
+ if (!this.isLocalProjectIndexPath()) {
7463
+ return "Keyword index could not be read from an inherited project index; semantic search remains available. Restore or repair it from the checkout that owns the index; do not rebuild it from this worktree.";
7464
+ }
7465
+ return "Keyword index could not be read; semantic search remains available. Restore a readable published keyword index, or run index_codebase with force=true after the active writer finishes.";
7466
+ }
7467
+ getDatabaseReadIssueMessage() {
7468
+ if (this.config.scope === "global") {
7469
+ return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
7470
+ }
7471
+ if (!this.isLocalProjectIndexPath()) {
7472
+ return "Index database could not be read from an inherited project index. Restore or repair it from the checkout that owns the index; do not migrate or rebuild it from this worktree.";
7473
+ }
7474
+ return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
7475
+ }
7476
+ getReaderFileFingerprint(filePath, identityOnly = false) {
7477
+ try {
7478
+ const stats = (0, import_fs8.statSync)(filePath);
7479
+ if (identityOnly) {
7480
+ return `${stats.dev}:${stats.ino}`;
7481
+ }
7482
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
7483
+ } catch (error) {
7484
+ return `unavailable:${getErrorMessage2(error)}`;
7485
+ }
7486
+ }
7487
+ captureReaderArtifactFingerprint() {
7488
+ const storePath = path13.join(this.indexPath, "vectors");
7489
+ return {
7490
+ vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7491
+ keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7492
+ database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7493
+ databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7494
+ };
7495
+ }
7496
+ refreshReaderArtifacts() {
7497
+ if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
7498
+ return;
7499
+ }
7500
+ const previousFingerprint = this.readerArtifactFingerprint;
7501
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7502
+ const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
7503
+ const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
7504
+ const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
7505
+ const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
7506
+ const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
7507
+ const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7508
+ if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
7509
+ return;
7510
+ }
7511
+ const setIssue = (component, message, error) => {
7512
+ if (!issues.has(component)) {
7513
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7514
+ }
7515
+ issues.set(component, this.createReadIssue(component, message));
7516
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7517
+ };
7518
+ const storePath = path13.join(this.indexPath, "vectors");
7519
+ const vectorMetadataPath = `${storePath}.meta.json`;
7520
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7521
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7522
+ if (vectorsChanged || retryDue("vectors")) {
7523
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7524
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7525
+ if (vectorStoreExists && vectorMetadataExists) {
7526
+ try {
7527
+ const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
7528
+ store.loadStrict();
7529
+ this.store = store;
7530
+ issues.delete("vectors");
7531
+ this.readerArtifactRetryAfter.delete("vectors");
7532
+ } catch (error) {
7533
+ setIssue("vectors", this.getVectorReadIssueMessage(), error);
7534
+ }
7535
+ } else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
7536
+ setIssue("vectors", this.getVectorReadIssueMessage());
7537
+ }
7538
+ }
7539
+ if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7540
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7541
+ try {
7542
+ const invertedIndex = new InvertedIndex(invertedIndexPath);
7543
+ invertedIndex.load();
7544
+ this.invertedIndex = invertedIndex;
7545
+ issues.delete("keyword");
7546
+ this.readerArtifactRetryAfter.delete("keyword");
7547
+ } catch (error) {
7548
+ setIssue("keyword", this.getKeywordReadIssueMessage(), error);
7549
+ }
7550
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
7551
+ setIssue("keyword", this.getKeywordReadIssueMessage());
7552
+ }
7553
+ }
7554
+ if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7555
+ if ((0, import_fs8.existsSync)(dbPath)) {
7556
+ try {
7557
+ const database = Database.openReadOnly(dbPath);
7558
+ if (this.database) {
7559
+ this.retiredDatabases.push(this.database);
7560
+ }
7561
+ this.database = database;
7562
+ issues.delete("database");
7563
+ this.readerArtifactRetryAfter.delete("database");
7564
+ } catch (error) {
7565
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7566
+ }
7567
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
7568
+ setIssue("database", this.getDatabaseReadIssueMessage());
7569
+ }
7570
+ }
7571
+ if (!issues.has("database")) {
7572
+ try {
7573
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7574
+ } catch (error) {
7575
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7576
+ }
7577
+ }
7578
+ this.readIssues = Array.from(issues.values());
7579
+ this.readerArtifactFingerprint = currentFingerprint;
7580
+ }
7581
+ refreshInactiveWriterArtifacts() {
7582
+ if (this.initializationMode !== "writer" || this.activeIndexLease) {
7583
+ return true;
7584
+ }
7585
+ const previousFingerprint = this.writerArtifactFingerprint;
7586
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7587
+ const retryDue = this.readIssues.some(
7588
+ (issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
7589
+ );
7590
+ const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7591
+ if (!artifactsChanged && !retryDue) {
7592
+ return true;
7593
+ }
7594
+ if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
7595
+ return false;
7596
+ }
7597
+ this.initializationMode = "reader";
7598
+ this.readerArtifactFingerprint = previousFingerprint;
7599
+ try {
7600
+ this.refreshReaderArtifacts();
7601
+ this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
7602
+ } finally {
7603
+ this.readerArtifactFingerprint = null;
7604
+ this.initializationMode = "writer";
7605
+ }
7606
+ return true;
7607
+ }
7608
+ async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
7609
+ if (mode === "writer") {
7610
+ this.requireActiveLease();
7611
+ }
7612
+ this.readIssues = [];
7613
+ this.readerArtifactRetryAfter.clear();
6840
7614
  if (this.config.embeddingProvider === "custom") {
6841
7615
  if (!this.config.customProvider) {
6842
7616
  throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
@@ -6868,36 +7642,103 @@ var Indexer = class {
6868
7642
  });
6869
7643
  }
6870
7644
  }
6871
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6872
7645
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6873
- const storePath = path12.join(this.indexPath, "vectors");
6874
- this.store = new VectorStore(storePath, dimensions);
6875
- const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
6876
- if ((0, import_fs7.existsSync)(indexFilePath)) {
6877
- this.store.load();
6878
- }
6879
- const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
6880
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6881
- try {
6882
- this.invertedIndex.load();
6883
- } catch {
6884
- if ((0, import_fs7.existsSync)(invertedIndexPath)) {
6885
- await import_fs7.promises.unlink(invertedIndexPath);
7646
+ const storePath = path13.join(this.indexPath, "vectors");
7647
+ const vectorMetadataPath = `${storePath}.meta.json`;
7648
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7649
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7650
+ let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
7651
+ const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7652
+ if (mode === "writer") {
7653
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7654
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7655
+ throw new Error(
7656
+ "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
7657
+ );
7658
+ }
7659
+ for (const recoveredOwner of recoveredOwners) {
7660
+ recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
7661
+ storePath,
7662
+ `${storePath}.meta.json`
7663
+ ]);
7664
+ }
7665
+ if (recoveredOwners.length > 0 && this.config.scope === "project") {
7666
+ await this.resetLocalIndexArtifacts();
7667
+ }
7668
+ this.store = new VectorStore(storePath, dimensions);
7669
+ if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
7670
+ this.store.load();
6886
7671
  }
6887
7672
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6888
- }
6889
- const dbPath = path12.join(this.indexPath, "codebase.db");
6890
- let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6891
- try {
6892
- this.database = new Database(dbPath);
6893
- } catch (error) {
6894
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6895
- throw error;
7673
+ try {
7674
+ this.invertedIndex.load();
7675
+ } catch {
7676
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7677
+ await import_fs8.promises.unlink(invertedIndexPath);
7678
+ }
7679
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
6896
7680
  }
7681
+ try {
7682
+ this.database = new Database(dbPath);
7683
+ } catch (error) {
7684
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7685
+ throw error;
7686
+ }
7687
+ this.store = new VectorStore(storePath, dimensions);
7688
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7689
+ this.database = new Database(dbPath);
7690
+ dbIsNew = true;
7691
+ }
7692
+ } else {
6897
7693
  this.store = new VectorStore(storePath, dimensions);
7694
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7695
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7696
+ const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7697
+ if (vectorStoreExists !== vectorMetadataExists) {
7698
+ this.recordReadIssue("vectors", vectorReadFailureMessage);
7699
+ } else if (vectorStoreExists) {
7700
+ try {
7701
+ this.store.loadStrict();
7702
+ } catch (error) {
7703
+ this.recordReadIssue("vectors", vectorReadFailureMessage, error);
7704
+ this.store = new VectorStore(storePath, dimensions);
7705
+ }
7706
+ }
6898
7707
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6899
- this.database = new Database(dbPath);
6900
- dbIsNew = true;
7708
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7709
+ try {
7710
+ this.invertedIndex.load();
7711
+ } catch (error) {
7712
+ this.recordReadIssue(
7713
+ "keyword",
7714
+ this.getKeywordReadIssueMessage(),
7715
+ error
7716
+ );
7717
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7718
+ }
7719
+ } else if (this.store.count() > 0) {
7720
+ this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7721
+ }
7722
+ if ((0, import_fs8.existsSync)(dbPath)) {
7723
+ try {
7724
+ this.database = Database.openReadOnly(dbPath);
7725
+ } catch (error) {
7726
+ this.recordReadIssue(
7727
+ "database",
7728
+ this.getDatabaseReadIssueMessage(),
7729
+ error
7730
+ );
7731
+ this.database = Database.createEmptyReadOnly();
7732
+ }
7733
+ } else {
7734
+ this.database = Database.createEmptyReadOnly();
7735
+ if (this.store.count() > 0) {
7736
+ this.recordReadIssue(
7737
+ "database",
7738
+ `Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
7739
+ );
7740
+ }
7741
+ }
6901
7742
  }
6902
7743
  if (isGitRepo(this.projectRoot)) {
6903
7744
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6911,10 +7752,10 @@ var Indexer = class {
6911
7752
  this.baseBranch = "default";
6912
7753
  this.logger.branch("debug", "Not a git repository, using default branch");
6913
7754
  }
6914
- if (this.checkForInterruptedIndexing()) {
6915
- await this.recoverFromInterruptedIndexing();
7755
+ if (mode === "writer" && recoveredOwners.length > 0) {
7756
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6916
7757
  }
6917
- if (dbIsNew && this.store.count() > 0) {
7758
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6918
7759
  this.migrateFromLegacyIndex();
6919
7760
  }
6920
7761
  this.loadFileHashCache();
@@ -6926,9 +7767,11 @@ var Indexer = class {
6926
7767
  configuredProviderInfo: this.configuredProviderInfo
6927
7768
  });
6928
7769
  }
6929
- if (this.config.indexing.autoGc) {
7770
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6930
7771
  await this.maybeRunAutoGc();
6931
7772
  }
7773
+ this.initializationMode = mode;
7774
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6932
7775
  }
6933
7776
  async maybeRunAutoGc() {
6934
7777
  if (!this.database) return;
@@ -6945,7 +7788,7 @@ var Indexer = class {
6945
7788
  }
6946
7789
  }
6947
7790
  if (shouldRunGc) {
6948
- const result = await this.healthCheck();
7791
+ const result = await this.healthCheckUnlocked();
6949
7792
  if (result.warning) {
6950
7793
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
6951
7794
  } else {
@@ -6967,7 +7810,7 @@ var Indexer = class {
6967
7810
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6968
7811
  return {
6969
7812
  resetCorruptedIndex: true,
6970
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
7813
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
6971
7814
  };
6972
7815
  }
6973
7816
  throw error;
@@ -6982,28 +7825,29 @@ var Indexer = class {
6982
7825
  return;
6983
7826
  }
6984
7827
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6985
- const storeBasePath = path12.join(this.indexPath, "vectors");
6986
- const storeIndexPath = `${storeBasePath}.usearch`;
7828
+ const storeBasePath = path13.join(this.indexPath, "vectors");
7829
+ const storeIndexPath = storeBasePath;
6987
7830
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6988
- const backupIndexPath = `${storeIndexPath}.bak`;
6989
- const backupMetadataPath = `${storeMetadataPath}.bak`;
7831
+ const lease = this.requireActiveLease();
7832
+ const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
7833
+ const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
6990
7834
  let backedUpIndex = false;
6991
7835
  let backedUpMetadata = false;
6992
7836
  let rebuiltCount = 0;
6993
7837
  let skippedCount = 0;
6994
- if ((0, import_fs7.existsSync)(backupIndexPath)) {
6995
- (0, import_fs7.unlinkSync)(backupIndexPath);
7838
+ if ((0, import_fs8.existsSync)(backupIndexPath)) {
7839
+ (0, import_fs8.unlinkSync)(backupIndexPath);
6996
7840
  }
6997
- if ((0, import_fs7.existsSync)(backupMetadataPath)) {
6998
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7841
+ if ((0, import_fs8.existsSync)(backupMetadataPath)) {
7842
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
6999
7843
  }
7000
7844
  try {
7001
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
7002
- (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
7845
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7846
+ (0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
7003
7847
  backedUpIndex = true;
7004
7848
  }
7005
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7006
- (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
7849
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7850
+ (0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
7007
7851
  backedUpMetadata = true;
7008
7852
  }
7009
7853
  store.clear();
@@ -7023,11 +7867,11 @@ var Indexer = class {
7023
7867
  rebuiltCount += 1;
7024
7868
  }
7025
7869
  store.save();
7026
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7027
- (0, import_fs7.unlinkSync)(backupIndexPath);
7870
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7871
+ (0, import_fs8.unlinkSync)(backupIndexPath);
7028
7872
  }
7029
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7030
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7873
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7874
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
7031
7875
  }
7032
7876
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
7033
7877
  excludedChunks: excludedSet.size,
@@ -7039,17 +7883,17 @@ var Indexer = class {
7039
7883
  store.clear();
7040
7884
  } catch {
7041
7885
  }
7042
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
7043
- (0, import_fs7.unlinkSync)(storeIndexPath);
7886
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7887
+ (0, import_fs8.unlinkSync)(storeIndexPath);
7044
7888
  }
7045
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7046
- (0, import_fs7.unlinkSync)(storeMetadataPath);
7889
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7890
+ (0, import_fs8.unlinkSync)(storeMetadataPath);
7047
7891
  }
7048
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7049
- (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
7892
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7893
+ (0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
7050
7894
  }
7051
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7052
- (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
7895
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7896
+ (0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
7053
7897
  }
7054
7898
  if (backedUpIndex || backedUpMetadata) {
7055
7899
  store.load();
@@ -7063,11 +7907,37 @@ var Indexer = class {
7063
7907
  }
7064
7908
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
7065
7909
  }
7910
+ async resetLocalIndexArtifacts() {
7911
+ this.store = null;
7912
+ this.invertedIndex = null;
7913
+ this.database?.close();
7914
+ this.database = null;
7915
+ this.indexCompatibility = null;
7916
+ this.initializationMode = "none";
7917
+ this.readIssues = [];
7918
+ this.readerArtifactFingerprint = null;
7919
+ this.writerArtifactFingerprint = null;
7920
+ this.readerArtifactRetryAfter.clear();
7921
+ this.fileHashCache.clear();
7922
+ const resetPaths = [
7923
+ path13.join(this.indexPath, "codebase.db"),
7924
+ path13.join(this.indexPath, "codebase.db-shm"),
7925
+ path13.join(this.indexPath, "codebase.db-wal"),
7926
+ path13.join(this.indexPath, "vectors"),
7927
+ path13.join(this.indexPath, "vectors.usearch"),
7928
+ path13.join(this.indexPath, "vectors.meta.json"),
7929
+ path13.join(this.indexPath, "inverted-index.json"),
7930
+ path13.join(this.indexPath, "file-hashes.json"),
7931
+ path13.join(this.indexPath, "failed-batches.json")
7932
+ ];
7933
+ await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
7934
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7935
+ }
7066
7936
  async tryResetCorruptedIndex(stage, error) {
7067
7937
  if (!isSqliteCorruptionError(error)) {
7068
7938
  return false;
7069
7939
  }
7070
- const dbPath = path12.join(this.indexPath, "codebase.db");
7940
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7071
7941
  const warning = this.getCorruptedIndexWarning(dbPath);
7072
7942
  const errorMessage = getErrorMessage2(error);
7073
7943
  if (this.config.scope === "global") {
@@ -7083,30 +7953,7 @@ var Indexer = class {
7083
7953
  dbPath,
7084
7954
  error: errorMessage
7085
7955
  });
7086
- this.store = null;
7087
- this.invertedIndex = null;
7088
- this.database?.close();
7089
- this.database = null;
7090
- this.indexCompatibility = null;
7091
- this.fileHashCache.clear();
7092
- const resetPaths = [
7093
- path12.join(this.indexPath, "codebase.db"),
7094
- path12.join(this.indexPath, "codebase.db-shm"),
7095
- path12.join(this.indexPath, "codebase.db-wal"),
7096
- path12.join(this.indexPath, "vectors.usearch"),
7097
- path12.join(this.indexPath, "inverted-index.json"),
7098
- path12.join(this.indexPath, "file-hashes.json"),
7099
- path12.join(this.indexPath, "failed-batches.json"),
7100
- path12.join(this.indexPath, "indexing.lock"),
7101
- path12.join(this.indexPath, "vectors")
7102
- ];
7103
- await Promise.all(resetPaths.map(async (targetPath) => {
7104
- try {
7105
- await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
7106
- } catch {
7107
- }
7108
- }));
7109
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
7956
+ await this.resetLocalIndexArtifacts();
7110
7957
  return true;
7111
7958
  }
7112
7959
  migrateFromLegacyIndex() {
@@ -7225,8 +8072,62 @@ var Indexer = class {
7225
8072
  return this.indexCompatibility;
7226
8073
  }
7227
8074
  async ensureInitialized() {
8075
+ let initializedReader = false;
8076
+ while (true) {
8077
+ if (this.initializationPromise) {
8078
+ await this.initializationPromise;
8079
+ }
8080
+ if (!this.isInitializedFor("reader")) {
8081
+ await this.initialize();
8082
+ initializedReader = true;
8083
+ continue;
8084
+ }
8085
+ if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
8086
+ if (!this.refreshInactiveWriterArtifacts()) {
8087
+ this.resetLoadedIndexState(true);
8088
+ await this.initialize();
8089
+ initializedReader = true;
8090
+ continue;
8091
+ }
8092
+ }
8093
+ if (this.initializationMode === "reader" && !initializedReader) {
8094
+ this.refreshReaderArtifacts();
8095
+ }
8096
+ const state = this.requireLoadedIndexState();
8097
+ return {
8098
+ ...state,
8099
+ readIssues: [...this.readIssues],
8100
+ compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
8101
+ };
8102
+ }
8103
+ }
8104
+ async ensureInitializedUnlocked(recoveredOwners = []) {
8105
+ this.requireActiveLease();
8106
+ if (this.initializationPromise) {
8107
+ await this.initializationPromise;
8108
+ }
8109
+ if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
8110
+ const retireReaderDatabase = this.initializationMode === "reader";
8111
+ this.resetLoadedIndexState(retireReaderDatabase);
8112
+ await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
8113
+ } else {
8114
+ this.refreshLoadedIndexState();
8115
+ }
8116
+ if (this.config.indexing.autoGc) {
8117
+ await this.maybeRunAutoGc();
8118
+ }
8119
+ return this.requireLoadedIndexState();
8120
+ }
8121
+ requireReadableComponents(readIssues, ...components) {
8122
+ const componentSet = new Set(components);
8123
+ const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
8124
+ if (issues.length > 0) {
8125
+ throw new Error(issues.map((issue) => issue.message).join(" "));
8126
+ }
8127
+ }
8128
+ requireLoadedIndexState() {
7228
8129
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7229
- await this.initialize();
8130
+ throw new Error("Index state is not initialized");
7230
8131
  }
7231
8132
  return {
7232
8133
  store: this.store,
@@ -7250,7 +8151,12 @@ var Indexer = class {
7250
8151
  return createCostEstimate(files, configuredProviderInfo);
7251
8152
  }
7252
8153
  async index(onProgress) {
7253
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8154
+ return this.withIndexMutationLease("index", async (recoveredOwners) => {
8155
+ return this.indexUnlocked(onProgress, recoveredOwners);
8156
+ });
8157
+ }
8158
+ async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
8159
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
7254
8160
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7255
8161
  const branchCatalogKey = this.getBranchCatalogKey();
7256
8162
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7260,7 +8166,6 @@ var Indexer = class {
7260
8166
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7261
8167
  );
7262
8168
  }
7263
- this.acquireIndexingLock();
7264
8169
  this.logger.recordIndexingStart();
7265
8170
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7266
8171
  const startTime = Date.now();
@@ -7311,7 +8216,7 @@ var Indexer = class {
7311
8216
  unchangedFilePaths.add(f.path);
7312
8217
  this.logger.recordCacheHit();
7313
8218
  } else {
7314
- const content = await import_fs7.promises.readFile(f.path, "utf-8");
8219
+ const content = await import_fs8.promises.readFile(f.path, "utf-8");
7315
8220
  changedFiles.push({ path: f.path, content, hash: currentHash });
7316
8221
  this.logger.recordCacheMiss();
7317
8222
  }
@@ -7409,7 +8314,7 @@ var Indexer = class {
7409
8314
  for (const parsed of parsedFiles) {
7410
8315
  currentFilePaths.add(parsed.path);
7411
8316
  if (parsed.chunks.length === 0) {
7412
- const relativePath = path12.relative(this.projectRoot, parsed.path);
8317
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
7413
8318
  stats.parseFailures.push(relativePath);
7414
8319
  }
7415
8320
  let fileChunkCount = 0;
@@ -7609,7 +8514,9 @@ var Indexer = class {
7609
8514
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7610
8515
  database.clearBranchSymbols(branchCatalogKey);
7611
8516
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7612
- if (backfilledBlameMetadata) {
8517
+ const vectorPath = path13.join(this.indexPath, "vectors");
8518
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
8519
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
7613
8520
  store.save();
7614
8521
  }
7615
8522
  if (scopedRoots) {
@@ -7630,7 +8537,6 @@ var Indexer = class {
7630
8537
  chunksProcessed: 0,
7631
8538
  totalChunks: 0
7632
8539
  });
7633
- this.releaseIndexingLock();
7634
8540
  return stats;
7635
8541
  }
7636
8542
  if (pendingChunks.length === 0) {
@@ -7639,7 +8545,7 @@ var Indexer = class {
7639
8545
  database.clearBranchSymbols(branchCatalogKey);
7640
8546
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7641
8547
  store.save();
7642
- invertedIndex.save();
8548
+ this.saveInvertedIndex(invertedIndex);
7643
8549
  if (scopedRoots) {
7644
8550
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7645
8551
  this.clearScopedFailedBatches(scopedRoots);
@@ -7658,7 +8564,6 @@ var Indexer = class {
7658
8564
  chunksProcessed: 0,
7659
8565
  totalChunks: 0
7660
8566
  });
7661
- this.releaseIndexingLock();
7662
8567
  return stats;
7663
8568
  }
7664
8569
  onProgress?.({
@@ -7889,7 +8794,7 @@ var Indexer = class {
7889
8794
  database.clearBranchSymbols(branchCatalogKey);
7890
8795
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7891
8796
  store.save();
7892
- invertedIndex.save();
8797
+ this.saveInvertedIndex(invertedIndex);
7893
8798
  if (scopedRoots) {
7894
8799
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7895
8800
  } else {
@@ -7941,7 +8846,6 @@ var Indexer = class {
7941
8846
  chunksProcessed: stats.indexedChunks,
7942
8847
  totalChunks: pendingChunks.length
7943
8848
  });
7944
- this.releaseIndexingLock();
7945
8849
  return stats;
7946
8850
  }
7947
8851
  async getQueryEmbedding(query, provider) {
@@ -8007,8 +8911,8 @@ var Indexer = class {
8007
8911
  return intersection / union;
8008
8912
  }
8009
8913
  async search(query, limit, options) {
8010
- const { store, provider, database } = await this.ensureInitialized();
8011
- const compatibility = this.checkCompatibility();
8914
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8915
+ this.requireReadableComponents(readIssues, "vectors", "database");
8012
8916
  if (!compatibility.compatible) {
8013
8917
  throw new Error(
8014
8918
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
@@ -8022,6 +8926,7 @@ var Indexer = class {
8022
8926
  const maxResults = limit ?? this.config.search.maxResults;
8023
8927
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
8024
8928
  const fusionStrategy = this.config.search.fusionStrategy;
8929
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
8025
8930
  const rrfK = this.config.search.rrfK;
8026
8931
  const rerankTopN = this.config.search.rerankTopN;
8027
8932
  const filterByBranch = options?.filterByBranch ?? true;
@@ -8030,7 +8935,7 @@ var Indexer = class {
8030
8935
  this.logger.search("debug", "Starting search", {
8031
8936
  query,
8032
8937
  maxResults,
8033
- hybridWeight,
8938
+ hybridWeight: effectiveHybridWeight,
8034
8939
  fusionStrategy,
8035
8940
  rrfK,
8036
8941
  rerankTopN,
@@ -8044,7 +8949,7 @@ var Indexer = class {
8044
8949
  const semanticResults = store.search(embedding, maxResults * 4);
8045
8950
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
8046
8951
  const keywordStartTime = import_perf_hooks.performance.now();
8047
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8952
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
8048
8953
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
8049
8954
  let branchChunkIds = null;
8050
8955
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -8081,7 +8986,7 @@ var Indexer = class {
8081
8986
  rrfK,
8082
8987
  rerankTopN,
8083
8988
  limit: maxResults,
8084
- hybridWeight,
8989
+ hybridWeight: effectiveHybridWeight,
8085
8990
  prioritizeSourcePaths: sourceIntent
8086
8991
  });
8087
8992
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -8155,7 +9060,7 @@ var Indexer = class {
8155
9060
  let contextEndLine = r.metadata.endLine;
8156
9061
  if (!metadataOnly && this.config.search.includeContext) {
8157
9062
  try {
8158
- const fileContent = await import_fs7.promises.readFile(
9063
+ const fileContent = await import_fs8.promises.readFile(
8159
9064
  r.metadata.filePath,
8160
9065
  "utf-8"
8161
9066
  );
@@ -8181,8 +9086,7 @@ var Indexer = class {
8181
9086
  })
8182
9087
  );
8183
9088
  }
8184
- async keywordSearch(query, limit) {
8185
- const { store, invertedIndex } = await this.ensureInitialized();
9089
+ async keywordSearch(query, limit, store, invertedIndex) {
8186
9090
  const scores = invertedIndex.search(query);
8187
9091
  if (scores.size === 0) {
8188
9092
  return [];
@@ -8200,24 +9104,54 @@ var Indexer = class {
8200
9104
  return results.slice(0, limit);
8201
9105
  }
8202
9106
  async getStatus() {
8203
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9107
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
8204
9108
  const failedBatchesCount = this.getFailedBatchesCount();
9109
+ const vectorCount = store.count();
9110
+ const statusReadIssues = [...readIssues];
9111
+ let startupWarning = "";
9112
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9113
+ try {
9114
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9115
+ } catch (error) {
9116
+ const message = this.getDatabaseReadIssueMessage();
9117
+ statusReadIssues.push(this.createReadIssue("database", message));
9118
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9119
+ this.recordReadIssue("database", message, error);
9120
+ }
9121
+ }
9122
+ }
9123
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9124
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9125
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
8205
9126
  return {
8206
- indexed: store.count() > 0,
8207
- vectorCount: store.count(),
9127
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9128
+ vectorCount,
8208
9129
  provider: configuredProviderInfo.provider,
8209
9130
  model: configuredProviderInfo.modelInfo.model,
8210
9131
  indexPath: this.indexPath,
8211
9132
  currentBranch: this.currentBranch,
8212
9133
  baseBranch: this.baseBranch,
8213
- compatibility: this.indexCompatibility,
9134
+ compatibility,
8214
9135
  failedBatchesCount,
8215
9136
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8216
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9137
+ warning: warning || void 0
8217
9138
  };
8218
9139
  }
9140
+ async forceIndex(onProgress) {
9141
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9142
+ await this.ensureInitializedUnlocked(recoveredOwners);
9143
+ await this.clearIndexUnlocked();
9144
+ return this.indexUnlocked(onProgress, [], true);
9145
+ });
9146
+ }
8219
9147
  async clearIndex() {
8220
- const { store, invertedIndex, database } = await this.ensureInitialized();
9148
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9149
+ await this.ensureInitializedUnlocked(recoveredOwners);
9150
+ await this.clearIndexUnlocked();
9151
+ });
9152
+ }
9153
+ async clearIndexUnlocked() {
9154
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8221
9155
  if (this.config.scope === "global") {
8222
9156
  store.load();
8223
9157
  invertedIndex.load();
@@ -8244,7 +9178,7 @@ var Indexer = class {
8244
9178
  store.clear();
8245
9179
  store.save();
8246
9180
  invertedIndex.clear();
8247
- invertedIndex.save();
9181
+ this.saveInvertedIndex(invertedIndex);
8248
9182
  this.fileHashCache.clear();
8249
9183
  this.saveFileHashCache();
8250
9184
  database.clearAllIndexedData();
@@ -8268,14 +9202,7 @@ var Indexer = class {
8268
9202
  this.indexCompatibility = compatibility;
8269
9203
  return;
8270
9204
  }
8271
- const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8272
- if (this.host !== "opencode") {
8273
- localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8274
- }
8275
- const isLocalProjectIndex = localProjectIndexPaths.some(
8276
- (localPath) => path12.resolve(this.indexPath) === path12.resolve(localPath)
8277
- );
8278
- if (!isLocalProjectIndex) {
9205
+ if (!this.isLocalProjectIndexPath()) {
8279
9206
  throw new Error(
8280
9207
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8281
9208
  );
@@ -8283,7 +9210,7 @@ var Indexer = class {
8283
9210
  store.clear();
8284
9211
  store.save();
8285
9212
  invertedIndex.clear();
8286
- invertedIndex.save();
9213
+ this.saveInvertedIndex(invertedIndex);
8287
9214
  this.fileHashCache.clear();
8288
9215
  this.saveFileHashCache();
8289
9216
  database.clearAllIndexedData();
@@ -8301,7 +9228,13 @@ var Indexer = class {
8301
9228
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8302
9229
  }
8303
9230
  async healthCheck() {
8304
- const { store, invertedIndex, database } = await this.ensureInitialized();
9231
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9232
+ await this.ensureInitializedUnlocked(recoveredOwners);
9233
+ return this.healthCheckUnlocked();
9234
+ });
9235
+ }
9236
+ async healthCheckUnlocked() {
9237
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8305
9238
  this.logger.gc("info", "Starting health check");
8306
9239
  const allMetadata = store.getAllMetadata();
8307
9240
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8314,7 +9247,7 @@ var Indexer = class {
8314
9247
  const removedChunkKeys = [];
8315
9248
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8316
9249
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8317
- if (!(0, import_fs7.existsSync)(filePath)) {
9250
+ if (!(0, import_fs8.existsSync)(filePath)) {
8318
9251
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8319
9252
  for (const key of chunkKeys) {
8320
9253
  removedChunkKeys.push(key);
@@ -8339,7 +9272,7 @@ var Indexer = class {
8339
9272
  const removedCount = removedChunkKeys.length;
8340
9273
  if (removedCount > 0) {
8341
9274
  store.save();
8342
- invertedIndex.save();
9275
+ this.saveInvertedIndex(invertedIndex);
8343
9276
  }
8344
9277
  let gcOrphanEmbeddings;
8345
9278
  let gcOrphanChunks;
@@ -8354,7 +9287,7 @@ var Indexer = class {
8354
9287
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8355
9288
  throw error;
8356
9289
  }
8357
- await this.ensureInitialized();
9290
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8358
9291
  return {
8359
9292
  removed: 0,
8360
9293
  filePaths: [],
@@ -8363,7 +9296,7 @@ var Indexer = class {
8363
9296
  gcOrphanSymbols: 0,
8364
9297
  gcOrphanCallEdges: 0,
8365
9298
  resetCorruptedIndex: true,
8366
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9299
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8367
9300
  };
8368
9301
  }
8369
9302
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8376,7 +9309,13 @@ var Indexer = class {
8376
9309
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8377
9310
  }
8378
9311
  async retryFailedBatches() {
8379
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9312
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9313
+ await this.ensureInitializedUnlocked(recoveredOwners);
9314
+ return this.retryFailedBatchesUnlocked();
9315
+ });
9316
+ }
9317
+ async retryFailedBatchesUnlocked() {
9318
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8380
9319
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8381
9320
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8382
9321
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8540,7 +9479,7 @@ var Indexer = class {
8540
9479
  }
8541
9480
  if (succeeded > 0) {
8542
9481
  store.save();
8543
- invertedIndex.save();
9482
+ this.saveInvertedIndex(invertedIndex);
8544
9483
  }
8545
9484
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8546
9485
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8568,15 +9507,16 @@ var Indexer = class {
8568
9507
  }
8569
9508
  }
8570
9509
  async getDatabaseStats() {
8571
- const { database } = await this.ensureInitialized();
9510
+ const { database, readIssues } = await this.ensureInitialized();
9511
+ this.requireReadableComponents(readIssues, "database");
8572
9512
  return database.getStats();
8573
9513
  }
8574
9514
  getLogger() {
8575
9515
  return this.logger;
8576
9516
  }
8577
9517
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8578
- const { store, provider, database } = await this.ensureInitialized();
8579
- const compatibility = this.checkCompatibility();
9518
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9519
+ this.requireReadableComponents(readIssues, "vectors", "database");
8580
9520
  if (!compatibility.compatible) {
8581
9521
  throw new Error(
8582
9522
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8666,7 +9606,7 @@ var Indexer = class {
8666
9606
  let content = "";
8667
9607
  if (this.config.search.includeContext) {
8668
9608
  try {
8669
- const fileContent = await import_fs7.promises.readFile(
9609
+ const fileContent = await import_fs8.promises.readFile(
8670
9610
  r.metadata.filePath,
8671
9611
  "utf-8"
8672
9612
  );
@@ -8690,7 +9630,8 @@ var Indexer = class {
8690
9630
  );
8691
9631
  }
8692
9632
  async getCallers(targetName, callTypeFilter) {
8693
- const { database } = await this.ensureInitialized();
9633
+ const { database, readIssues } = await this.ensureInitialized();
9634
+ this.requireReadableComponents(readIssues, "database");
8694
9635
  const seen = /* @__PURE__ */ new Set();
8695
9636
  const results = [];
8696
9637
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8704,7 +9645,8 @@ var Indexer = class {
8704
9645
  return results;
8705
9646
  }
8706
9647
  async getCallees(symbolId, callTypeFilter) {
8707
- const { database } = await this.ensureInitialized();
9648
+ const { database, readIssues } = await this.ensureInitialized();
9649
+ this.requireReadableComponents(readIssues, "database");
8708
9650
  const seen = /* @__PURE__ */ new Set();
8709
9651
  const results = [];
8710
9652
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8718,43 +9660,50 @@ var Indexer = class {
8718
9660
  return results;
8719
9661
  }
8720
9662
  async findCallPath(fromName, toName, maxDepth) {
8721
- const { database } = await this.ensureInitialized();
9663
+ const { database, readIssues } = await this.ensureInitialized();
9664
+ this.requireReadableComponents(readIssues, "database");
8722
9665
  let shortest = [];
8723
9666
  for (const branchKey of this.getBranchCatalogKeys()) {
8724
- const path23 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8725
- if (path23.length > 0 && (shortest.length === 0 || path23.length < shortest.length)) {
8726
- shortest = path23;
9667
+ const path24 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9668
+ if (path24.length > 0 && (shortest.length === 0 || path24.length < shortest.length)) {
9669
+ shortest = path24;
8727
9670
  }
8728
9671
  }
8729
9672
  return shortest;
8730
9673
  }
8731
9674
  async getSymbolsForBranch(branch) {
8732
- const { database } = await this.ensureInitialized();
9675
+ const { database, readIssues } = await this.ensureInitialized();
9676
+ this.requireReadableComponents(readIssues, "database");
8733
9677
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8734
9678
  return database.getSymbolsForBranch(resolvedBranch);
8735
9679
  }
8736
9680
  async getSymbolsForFiles(filePaths, branch) {
8737
- const { database } = await this.ensureInitialized();
9681
+ const { database, readIssues } = await this.ensureInitialized();
9682
+ this.requireReadableComponents(readIssues, "database");
8738
9683
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8739
9684
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8740
9685
  }
8741
9686
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8742
- const { database } = await this.ensureInitialized();
9687
+ const { database, readIssues } = await this.ensureInitialized();
9688
+ this.requireReadableComponents(readIssues, "database");
8743
9689
  const branch = this.getBranchCatalogKey();
8744
9690
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8745
9691
  }
8746
9692
  async detectCommunities(branch, symbolIds) {
8747
- const { database } = await this.ensureInitialized();
9693
+ const { database, readIssues } = await this.ensureInitialized();
9694
+ this.requireReadableComponents(readIssues, "database");
8748
9695
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8749
9696
  return database.detectCommunities(resolvedBranch, symbolIds);
8750
9697
  }
8751
9698
  async computeCentrality(branch) {
8752
- const { database } = await this.ensureInitialized();
9699
+ const { database, readIssues } = await this.ensureInitialized();
9700
+ this.requireReadableComponents(readIssues, "database");
8753
9701
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8754
9702
  return database.computeCentrality(resolvedBranch);
8755
9703
  }
8756
9704
  async getPrImpact(opts) {
8757
- const { database } = await this.ensureInitialized();
9705
+ const { database, readIssues } = await this.ensureInitialized();
9706
+ this.requireReadableComponents(readIssues, "database");
8758
9707
  const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8759
9708
  const changedFilesResult = await getChangedFiles({
8760
9709
  pr: opts.pr,
@@ -8777,7 +9726,7 @@ var Indexer = class {
8777
9726
  "Run index_codebase first to build the call graph and symbol index for this branch."
8778
9727
  );
8779
9728
  }
8780
- const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
9729
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8781
9730
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8782
9731
  const directIds = directSymbols.map((s) => s.id);
8783
9732
  const direction = opts.direction ?? "both";
@@ -8860,7 +9809,7 @@ var Indexer = class {
8860
9809
  projectRoot: this.projectRoot,
8861
9810
  baseBranch: this.baseBranch
8862
9811
  });
8863
- const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
9812
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8864
9813
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8865
9814
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8866
9815
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8910,7 +9859,8 @@ var Indexer = class {
8910
9859
  };
8911
9860
  }
8912
9861
  async getVisualizationData(options) {
8913
- const { database, store } = await this.ensureInitialized();
9862
+ const { database, store, readIssues } = await this.ensureInitialized();
9863
+ this.requireReadableComponents(readIssues, "vectors", "database");
8914
9864
  const seenSymbols = /* @__PURE__ */ new Map();
8915
9865
  const seenEdges = /* @__PURE__ */ new Map();
8916
9866
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8923,12 +9873,12 @@ var Indexer = class {
8923
9873
  if (meta.filePath) filePaths.add(meta.filePath);
8924
9874
  }
8925
9875
  const directory = options?.directory?.replace(/\/$/, "");
8926
- const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
9876
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8927
9877
  for (const filePath of filePaths) {
8928
9878
  if (directory) {
8929
- const absoluteFilePath = path12.resolve(filePath);
9879
+ const absoluteFilePath = path13.resolve(filePath);
8930
9880
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8931
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
9881
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8932
9882
  if (!matchesRelative && !matchesProjectRelative) {
8933
9883
  continue;
8934
9884
  }
@@ -8950,53 +9900,64 @@ var Indexer = class {
8950
9900
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8951
9901
  }
8952
9902
  async close() {
8953
- await this.database?.close();
9903
+ this.database?.close();
9904
+ for (const database of this.retiredDatabases) {
9905
+ database.close();
9906
+ }
9907
+ this.retiredDatabases = [];
8954
9908
  this.database = null;
8955
9909
  this.store = null;
8956
9910
  this.invertedIndex = null;
8957
9911
  this.provider = null;
8958
9912
  this.reranker = null;
9913
+ this.configuredProviderInfo = null;
9914
+ this.indexCompatibility = null;
9915
+ this.initializationMode = "none";
9916
+ this.readIssues = [];
9917
+ this.readerArtifactFingerprint = null;
9918
+ this.writerArtifactFingerprint = null;
9919
+ this.readerArtifactRetryAfter.clear();
8959
9920
  }
8960
9921
  };
8961
9922
 
8962
9923
  // src/tools/knowledge-base-paths.ts
8963
- var path13 = __toESM(require("path"), 1);
9924
+ var path14 = __toESM(require("path"), 1);
8964
9925
  function resolveConfigPathValue(value, baseDir) {
8965
9926
  const trimmed = value.trim();
8966
9927
  if (!trimmed) {
8967
9928
  return trimmed;
8968
9929
  }
8969
- const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
8970
- return path13.normalize(absolutePath);
9930
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9931
+ return path14.normalize(absolutePath);
8971
9932
  }
8972
9933
  function serializeConfigPathValue(value, baseDir) {
8973
9934
  const trimmed = value.trim();
8974
9935
  if (!trimmed) {
8975
9936
  return trimmed;
8976
9937
  }
8977
- if (!path13.isAbsolute(trimmed)) {
8978
- return normalizePathSeparators(path13.normalize(trimmed));
9938
+ if (!path14.isAbsolute(trimmed)) {
9939
+ return normalizePathSeparators(path14.normalize(trimmed));
8979
9940
  }
8980
- const relativePath = path13.relative(baseDir, trimmed);
8981
- if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
8982
- return normalizePathSeparators(path13.normalize(relativePath || "."));
9941
+ const relativePath = path14.relative(baseDir, trimmed);
9942
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9943
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8983
9944
  }
8984
- return path13.normalize(trimmed);
9945
+ return path14.normalize(trimmed);
8985
9946
  }
8986
9947
  function resolveKnowledgeBasePath(value, projectRoot) {
8987
- return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
9948
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
8988
9949
  }
8989
9950
  function normalizeKnowledgeBasePath2(value, projectRoot) {
8990
- return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
9951
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
8991
9952
  }
8992
9953
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
8993
- const normalizedInput = path13.normalize(inputPath);
9954
+ const normalizedInput = path14.normalize(inputPath);
8994
9955
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
8995
9956
  }
8996
9957
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
8997
- const normalizedInput = path13.normalize(inputPath);
9958
+ const normalizedInput = path14.normalize(inputPath);
8998
9959
  return knowledgeBases.findIndex(
8999
- (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
9960
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
9000
9961
  );
9001
9962
  }
9002
9963
 
@@ -9096,6 +10057,10 @@ function formatStatus(status) {
9096
10057
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
9097
10058
  }
9098
10059
  }
10060
+ if (status.warning) {
10061
+ lines.push("");
10062
+ lines.push(`INDEX WARNING: ${status.warning}`);
10063
+ }
9099
10064
  if (status.compatibility && !status.compatibility.compatible) {
9100
10065
  lines.push("");
9101
10066
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -9201,16 +10166,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
9201
10166
  return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
9202
10167
  }).join("\n");
9203
10168
  }
9204
- function formatCallGraphPath(from, to, path23) {
9205
- if (path23.length === 0) {
10169
+ function formatCallGraphPath(from, to, path24) {
10170
+ if (path24.length === 0) {
9206
10171
  return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
9207
10172
  }
9208
- const formatted = path23.map((hop, index) => {
10173
+ const formatted = path24.map((hop, index) => {
9209
10174
  const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
9210
10175
  const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
9211
10176
  return `${prefix} ${hop.symbolName}${location}`;
9212
10177
  });
9213
- return `Path (${path23.length} hops):
10178
+ return `Path (${path24.length} hops):
9214
10179
  ${formatted.join("\n")}`;
9215
10180
  }
9216
10181
  function formatResultHeader(result, index) {
@@ -9250,8 +10215,8 @@ ${truncateContent(r.content)}
9250
10215
  }
9251
10216
 
9252
10217
  // src/tools/config-state.ts
9253
- var import_fs8 = require("fs");
9254
- var path14 = __toESM(require("path"), 1);
10218
+ var import_fs9 = require("fs");
10219
+ var path15 = __toESM(require("path"), 1);
9255
10220
  function normalizeKnowledgeBasePaths(config, projectRoot) {
9256
10221
  const normalized = { ...config };
9257
10222
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -9278,10 +10243,10 @@ function loadEditableConfig(projectRoot, host = "opencode") {
9278
10243
  }
9279
10244
  function saveConfig(projectRoot, config, host = "opencode") {
9280
10245
  const configPath = getConfigPath(projectRoot, host);
9281
- const configDir = path14.dirname(configPath);
9282
- const configBaseDir = path14.dirname(configDir);
9283
- if (!(0, import_fs8.existsSync)(configDir)) {
9284
- (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10246
+ const configDir = path15.dirname(configPath);
10247
+ const configBaseDir = path15.dirname(configDir);
10248
+ if (!(0, import_fs9.existsSync)(configDir)) {
10249
+ (0, import_fs9.mkdirSync)(configDir, { recursive: true });
9285
10250
  }
9286
10251
  const serializableConfig = { ...config };
9287
10252
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9289,13 +10254,31 @@ function saveConfig(projectRoot, config, host = "opencode") {
9289
10254
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9290
10255
  );
9291
10256
  }
9292
- (0, import_fs8.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10257
+ (0, import_fs9.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9293
10258
  }
9294
10259
 
9295
10260
  // src/tools/operations.ts
9296
10261
  var indexerCache = /* @__PURE__ */ new Map();
9297
10262
  var configCache = /* @__PURE__ */ new Map();
9298
10263
  var defaultProjectRoots = /* @__PURE__ */ new Map();
10264
+ function getIndexBusyResult(error) {
10265
+ if (!isIndexLockContentionError(error)) return null;
10266
+ const owner = error.owner;
10267
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10268
+ if (error.reason === "legacy-lock") {
10269
+ return {
10270
+ kind: "busy",
10271
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10272
+ };
10273
+ }
10274
+ if (error.reason === "unknown-owner") {
10275
+ return {
10276
+ kind: "busy",
10277
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10278
+ };
10279
+ }
10280
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10281
+ }
9299
10282
  function getProjectRoot(projectRoot, host) {
9300
10283
  if (projectRoot) {
9301
10284
  return projectRoot;
@@ -9340,8 +10323,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
9340
10323
  }
9341
10324
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
9342
10325
  const root = getProjectRoot(projectRoot, host);
9343
- const localIndexPath = path15.join(root, getHostProjectIndexRelativePath(host));
9344
- if ((0, import_fs9.existsSync)(localIndexPath)) {
10326
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10327
+ if ((0, import_fs10.existsSync)(localIndexPath)) {
9345
10328
  return false;
9346
10329
  }
9347
10330
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
@@ -9397,35 +10380,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
9397
10380
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
9398
10381
  const root = getProjectRoot(projectRoot, host);
9399
10382
  const key = getIndexerCacheKey(root, host);
9400
- const cachedConfig = configCache.get(key);
9401
10383
  let indexer = getIndexerForProject(root, host);
9402
- if (args.estimateOnly) {
9403
- return { kind: "estimate", estimate: await indexer.estimateCost() };
9404
- }
9405
- if (args.force) {
9406
- if (shouldForceLocalizeProjectIndex(root, host)) {
9407
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
9408
- refreshIndexerForDirectory(root, host, cachedConfig);
9409
- indexer = getIndexerForProject(root, host);
9410
- }
9411
- await indexer.clearIndex();
9412
- refreshIndexerForDirectory(root, host, cachedConfig);
9413
- indexer = getIndexerForProject(root, host);
9414
- }
9415
- const stats = await indexer.index((progress) => {
9416
- if (onProgress) {
9417
- return onProgress(formatProgressTitle(progress), {
9418
- phase: progress.phase,
9419
- filesProcessed: progress.filesProcessed,
9420
- totalFiles: progress.totalFiles,
9421
- chunksProcessed: progress.chunksProcessed,
9422
- totalChunks: progress.totalChunks,
9423
- percentage: calculatePercentage(progress)
10384
+ const runtimeConfig = configCache.get(key);
10385
+ try {
10386
+ if (args.estimateOnly) {
10387
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10388
+ }
10389
+ const runIndex = async (target) => {
10390
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10391
+ return operation((progress) => {
10392
+ if (onProgress) {
10393
+ return onProgress(formatProgressTitle(progress), {
10394
+ phase: progress.phase,
10395
+ filesProcessed: progress.filesProcessed,
10396
+ totalFiles: progress.totalFiles,
10397
+ chunksProcessed: progress.chunksProcessed,
10398
+ totalChunks: progress.totalChunks,
10399
+ percentage: calculatePercentage(progress)
10400
+ });
10401
+ }
10402
+ return Promise.resolve();
9424
10403
  });
10404
+ };
10405
+ let stats;
10406
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10407
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10408
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10409
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10410
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10411
+ indexer = getIndexerForProject(root, host);
10412
+ return runIndex(indexer);
10413
+ }, { completeRecoveries: false });
10414
+ } else {
10415
+ stats = await runIndex(indexer);
9425
10416
  }
9426
- return Promise.resolve();
9427
- });
9428
- return { kind: "stats", stats };
10417
+ return { kind: "stats", stats };
10418
+ } catch (error) {
10419
+ const busyResult = getIndexBusyResult(error);
10420
+ if (!busyResult) throw error;
10421
+ return busyResult;
10422
+ }
9429
10423
  }
9430
10424
  async function getIndexStatus(projectRoot, host) {
9431
10425
  const indexer = getIndexerForProject(projectRoot, host);
@@ -9435,6 +10429,15 @@ async function getIndexHealthCheck(projectRoot, host) {
9435
10429
  const indexer = getIndexerForProject(projectRoot, host);
9436
10430
  return indexer.healthCheck();
9437
10431
  }
10432
+ async function runIndexHealthCheck(projectRoot, host) {
10433
+ try {
10434
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
10435
+ } catch (error) {
10436
+ const busyResult = getIndexBusyResult(error);
10437
+ if (!busyResult) throw error;
10438
+ return busyResult;
10439
+ }
10440
+ }
9438
10441
  async function getIndexMetrics(projectRoot, host) {
9439
10442
  const indexer = getIndexerForProject(projectRoot, host);
9440
10443
  const logger = indexer.getLogger();
@@ -9490,15 +10493,15 @@ async function getIndexLogs(projectRoot, host, args) {
9490
10493
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9491
10494
  const root = getProjectRoot(projectRoot, host);
9492
10495
  const inputPath = knowledgeBasePath.trim();
9493
- const normalizedPath = path15.resolve(
9494
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10496
+ const normalizedPath = path16.resolve(
10497
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9495
10498
  );
9496
- if (!(0, import_fs9.existsSync)(normalizedPath)) {
10499
+ if (!(0, import_fs10.existsSync)(normalizedPath)) {
9497
10500
  return `Error: Directory does not exist: ${normalizedPath}`;
9498
10501
  }
9499
10502
  let realPath;
9500
10503
  try {
9501
- realPath = (0, import_fs9.realpathSync)(normalizedPath);
10504
+ realPath = (0, import_fs10.realpathSync)(normalizedPath);
9502
10505
  } catch {
9503
10506
  return `Error: Cannot resolve path: ${normalizedPath}`;
9504
10507
  }
@@ -9527,13 +10530,13 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9527
10530
  }
9528
10531
  }
9529
10532
  for (const dotDir of sensitiveDotDirs) {
9530
- const sensitiveDir = path15.join(homeDir, dotDir);
10533
+ const sensitiveDir = path16.join(homeDir, dotDir);
9531
10534
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9532
10535
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9533
10536
  }
9534
10537
  }
9535
10538
  try {
9536
- const stat4 = (0, import_fs9.statSync)(normalizedPath);
10539
+ const stat4 = (0, import_fs10.statSync)(normalizedPath);
9537
10540
  if (!stat4.isDirectory()) {
9538
10541
  return `Error: Path is not a directory: ${normalizedPath}`;
9539
10542
  }
@@ -9573,7 +10576,7 @@ function listKnowledgeBases(projectRoot, host) {
9573
10576
  for (let i = 0; i < knowledgeBases.length; i++) {
9574
10577
  const kb = knowledgeBases[i];
9575
10578
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9576
- const exists = (0, import_fs9.existsSync)(resolvedPath);
10579
+ const exists = (0, import_fs10.existsSync)(resolvedPath);
9577
10580
  result += `[${i + 1}] ${kb}
9578
10581
  `;
9579
10582
  result += ` Resolved: ${resolvedPath}
@@ -9582,7 +10585,7 @@ function listKnowledgeBases(projectRoot, host) {
9582
10585
  `;
9583
10586
  if (exists) {
9584
10587
  try {
9585
- const stat4 = (0, import_fs9.statSync)(resolvedPath);
10588
+ const stat4 = (0, import_fs10.statSync)(resolvedPath);
9586
10589
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
9587
10590
  `;
9588
10591
  } catch {
@@ -9590,7 +10593,7 @@ function listKnowledgeBases(projectRoot, host) {
9590
10593
  }
9591
10594
  result += "\n";
9592
10595
  }
9593
- const hasHostConfig = (0, import_fs9.existsSync)(path15.join(root, getHostProjectConfigRelativePath(host)));
10596
+ const hasHostConfig = (0, import_fs10.existsSync)(path16.join(root, getHostProjectConfigRelativePath(host)));
9594
10597
  if (hasHostConfig) {
9595
10598
  result += `
9596
10599
  Config sources: 1 file(s).`;
@@ -9713,7 +10716,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9713
10716
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9714
10717
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
9715
10718
  if (wantBigintFsStats) {
9716
- this._stat = (path23) => statMethod(path23, { bigint: true });
10719
+ this._stat = (path24) => statMethod(path24, { bigint: true });
9717
10720
  } else {
9718
10721
  this._stat = statMethod;
9719
10722
  }
@@ -9738,8 +10741,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9738
10741
  const par = this.parent;
9739
10742
  const fil = par && par.files;
9740
10743
  if (fil && fil.length > 0) {
9741
- const { path: path23, depth } = par;
9742
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
10744
+ const { path: path24, depth } = par;
10745
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
9743
10746
  const awaited = await Promise.all(slice);
9744
10747
  for (const entry of awaited) {
9745
10748
  if (!entry)
@@ -9779,20 +10782,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9779
10782
  this.reading = false;
9780
10783
  }
9781
10784
  }
9782
- async _exploreDir(path23, depth) {
10785
+ async _exploreDir(path24, depth) {
9783
10786
  let files;
9784
10787
  try {
9785
- files = await (0, import_promises.readdir)(path23, this._rdOptions);
10788
+ files = await (0, import_promises.readdir)(path24, this._rdOptions);
9786
10789
  } catch (error) {
9787
10790
  this._onError(error);
9788
10791
  }
9789
- return { files, depth, path: path23 };
10792
+ return { files, depth, path: path24 };
9790
10793
  }
9791
- async _formatEntry(dirent, path23) {
10794
+ async _formatEntry(dirent, path24) {
9792
10795
  let entry;
9793
10796
  const basename5 = this._isDirent ? dirent.name : dirent;
9794
10797
  try {
9795
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path23, basename5));
10798
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path24, basename5));
9796
10799
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
9797
10800
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9798
10801
  } catch (err) {
@@ -10192,16 +11195,16 @@ var delFromSet = (main, prop, item) => {
10192
11195
  };
10193
11196
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
10194
11197
  var FsWatchInstances = /* @__PURE__ */ new Map();
10195
- function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
11198
+ function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
10196
11199
  const handleEvent = (rawEvent, evPath) => {
10197
- listener(path23);
10198
- emitRaw(rawEvent, evPath, { watchedPath: path23 });
10199
- if (evPath && path23 !== evPath) {
10200
- fsWatchBroadcast(sp.resolve(path23, evPath), KEY_LISTENERS, sp.join(path23, evPath));
11200
+ listener(path24);
11201
+ emitRaw(rawEvent, evPath, { watchedPath: path24 });
11202
+ if (evPath && path24 !== evPath) {
11203
+ fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
10201
11204
  }
10202
11205
  };
10203
11206
  try {
10204
- return (0, import_node_fs.watch)(path23, {
11207
+ return (0, import_node_fs.watch)(path24, {
10205
11208
  persistent: options.persistent
10206
11209
  }, handleEvent);
10207
11210
  } catch (error) {
@@ -10217,12 +11220,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
10217
11220
  listener(val1, val2, val3);
10218
11221
  });
10219
11222
  };
10220
- var setFsWatchListener = (path23, fullPath, options, handlers) => {
11223
+ var setFsWatchListener = (path24, fullPath, options, handlers) => {
10221
11224
  const { listener, errHandler, rawEmitter } = handlers;
10222
11225
  let cont = FsWatchInstances.get(fullPath);
10223
11226
  let watcher;
10224
11227
  if (!options.persistent) {
10225
- watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
11228
+ watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
10226
11229
  if (!watcher)
10227
11230
  return;
10228
11231
  return watcher.close.bind(watcher);
@@ -10233,7 +11236,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10233
11236
  addAndConvert(cont, KEY_RAW, rawEmitter);
10234
11237
  } else {
10235
11238
  watcher = createFsWatchInstance(
10236
- path23,
11239
+ path24,
10237
11240
  options,
10238
11241
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10239
11242
  errHandler,
@@ -10248,7 +11251,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10248
11251
  cont.watcherUnusable = true;
10249
11252
  if (isWindows && error.code === "EPERM") {
10250
11253
  try {
10251
- const fd = await (0, import_promises2.open)(path23, "r");
11254
+ const fd = await (0, import_promises2.open)(path24, "r");
10252
11255
  await fd.close();
10253
11256
  broadcastErr(error);
10254
11257
  } catch (err) {
@@ -10279,7 +11282,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10279
11282
  };
10280
11283
  };
10281
11284
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10282
- var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
11285
+ var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
10283
11286
  const { listener, rawEmitter } = handlers;
10284
11287
  let cont = FsWatchFileInstances.get(fullPath);
10285
11288
  const copts = cont && cont.options;
@@ -10301,7 +11304,7 @@ var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
10301
11304
  });
10302
11305
  const currmtime = curr.mtimeMs;
10303
11306
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10304
- foreach(cont.listeners, (listener2) => listener2(path23, curr));
11307
+ foreach(cont.listeners, (listener2) => listener2(path24, curr));
10305
11308
  }
10306
11309
  })
10307
11310
  };
@@ -10331,13 +11334,13 @@ var NodeFsHandler = class {
10331
11334
  * @param listener on fs change
10332
11335
  * @returns closer for the watcher instance
10333
11336
  */
10334
- _watchWithNodeFs(path23, listener) {
11337
+ _watchWithNodeFs(path24, listener) {
10335
11338
  const opts = this.fsw.options;
10336
- const directory = sp.dirname(path23);
10337
- const basename5 = sp.basename(path23);
11339
+ const directory = sp.dirname(path24);
11340
+ const basename5 = sp.basename(path24);
10338
11341
  const parent = this.fsw._getWatchedDir(directory);
10339
11342
  parent.add(basename5);
10340
- const absolutePath = sp.resolve(path23);
11343
+ const absolutePath = sp.resolve(path24);
10341
11344
  const options = {
10342
11345
  persistent: opts.persistent
10343
11346
  };
@@ -10347,12 +11350,12 @@ var NodeFsHandler = class {
10347
11350
  if (opts.usePolling) {
10348
11351
  const enableBin = opts.interval !== opts.binaryInterval;
10349
11352
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
10350
- closer = setFsWatchFileListener(path23, absolutePath, options, {
11353
+ closer = setFsWatchFileListener(path24, absolutePath, options, {
10351
11354
  listener,
10352
11355
  rawEmitter: this.fsw._emitRaw
10353
11356
  });
10354
11357
  } else {
10355
- closer = setFsWatchListener(path23, absolutePath, options, {
11358
+ closer = setFsWatchListener(path24, absolutePath, options, {
10356
11359
  listener,
10357
11360
  errHandler: this._boundHandleError,
10358
11361
  rawEmitter: this.fsw._emitRaw
@@ -10374,7 +11377,7 @@ var NodeFsHandler = class {
10374
11377
  let prevStats = stats;
10375
11378
  if (parent.has(basename5))
10376
11379
  return;
10377
- const listener = async (path23, newStats) => {
11380
+ const listener = async (path24, newStats) => {
10378
11381
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10379
11382
  return;
10380
11383
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10388,11 +11391,11 @@ var NodeFsHandler = class {
10388
11391
  this.fsw._emit(EV.CHANGE, file, newStats2);
10389
11392
  }
10390
11393
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10391
- this.fsw._closeFile(path23);
11394
+ this.fsw._closeFile(path24);
10392
11395
  prevStats = newStats2;
10393
11396
  const closer2 = this._watchWithNodeFs(file, listener);
10394
11397
  if (closer2)
10395
- this.fsw._addPathCloser(path23, closer2);
11398
+ this.fsw._addPathCloser(path24, closer2);
10396
11399
  } else {
10397
11400
  prevStats = newStats2;
10398
11401
  }
@@ -10424,7 +11427,7 @@ var NodeFsHandler = class {
10424
11427
  * @param item basename of this item
10425
11428
  * @returns true if no more processing is needed for this entry.
10426
11429
  */
10427
- async _handleSymlink(entry, directory, path23, item) {
11430
+ async _handleSymlink(entry, directory, path24, item) {
10428
11431
  if (this.fsw.closed) {
10429
11432
  return;
10430
11433
  }
@@ -10434,7 +11437,7 @@ var NodeFsHandler = class {
10434
11437
  this.fsw._incrReadyCount();
10435
11438
  let linkPath;
10436
11439
  try {
10437
- linkPath = await (0, import_promises2.realpath)(path23);
11440
+ linkPath = await (0, import_promises2.realpath)(path24);
10438
11441
  } catch (e) {
10439
11442
  this.fsw._emitReady();
10440
11443
  return true;
@@ -10444,12 +11447,12 @@ var NodeFsHandler = class {
10444
11447
  if (dir.has(item)) {
10445
11448
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10446
11449
  this.fsw._symlinkPaths.set(full, linkPath);
10447
- this.fsw._emit(EV.CHANGE, path23, entry.stats);
11450
+ this.fsw._emit(EV.CHANGE, path24, entry.stats);
10448
11451
  }
10449
11452
  } else {
10450
11453
  dir.add(item);
10451
11454
  this.fsw._symlinkPaths.set(full, linkPath);
10452
- this.fsw._emit(EV.ADD, path23, entry.stats);
11455
+ this.fsw._emit(EV.ADD, path24, entry.stats);
10453
11456
  }
10454
11457
  this.fsw._emitReady();
10455
11458
  return true;
@@ -10479,9 +11482,9 @@ var NodeFsHandler = class {
10479
11482
  return;
10480
11483
  }
10481
11484
  const item = entry.path;
10482
- let path23 = sp.join(directory, item);
11485
+ let path24 = sp.join(directory, item);
10483
11486
  current.add(item);
10484
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
11487
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
10485
11488
  return;
10486
11489
  }
10487
11490
  if (this.fsw.closed) {
@@ -10490,8 +11493,8 @@ var NodeFsHandler = class {
10490
11493
  }
10491
11494
  if (item === target || !target && !previous.has(item)) {
10492
11495
  this.fsw._incrReadyCount();
10493
- path23 = sp.join(dir, sp.relative(dir, path23));
10494
- this._addToNodeFs(path23, initialAdd, wh, depth + 1);
11496
+ path24 = sp.join(dir, sp.relative(dir, path24));
11497
+ this._addToNodeFs(path24, initialAdd, wh, depth + 1);
10495
11498
  }
10496
11499
  }).on(EV.ERROR, this._boundHandleError);
10497
11500
  return new Promise((resolve13, reject) => {
@@ -10560,13 +11563,13 @@ var NodeFsHandler = class {
10560
11563
  * @param depth Child path actually targeted for watch
10561
11564
  * @param target Child path actually targeted for watch
10562
11565
  */
10563
- async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
11566
+ async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
10564
11567
  const ready = this.fsw._emitReady;
10565
- if (this.fsw._isIgnored(path23) || this.fsw.closed) {
11568
+ if (this.fsw._isIgnored(path24) || this.fsw.closed) {
10566
11569
  ready();
10567
11570
  return false;
10568
11571
  }
10569
- const wh = this.fsw._getWatchHelpers(path23);
11572
+ const wh = this.fsw._getWatchHelpers(path24);
10570
11573
  if (priorWh) {
10571
11574
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10572
11575
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10582,8 +11585,8 @@ var NodeFsHandler = class {
10582
11585
  const follow = this.fsw.options.followSymlinks;
10583
11586
  let closer;
10584
11587
  if (stats.isDirectory()) {
10585
- const absPath = sp.resolve(path23);
10586
- const targetPath = follow ? await (0, import_promises2.realpath)(path23) : path23;
11588
+ const absPath = sp.resolve(path24);
11589
+ const targetPath = follow ? await (0, import_promises2.realpath)(path24) : path24;
10587
11590
  if (this.fsw.closed)
10588
11591
  return;
10589
11592
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10593,29 +11596,29 @@ var NodeFsHandler = class {
10593
11596
  this.fsw._symlinkPaths.set(absPath, targetPath);
10594
11597
  }
10595
11598
  } else if (stats.isSymbolicLink()) {
10596
- const targetPath = follow ? await (0, import_promises2.realpath)(path23) : path23;
11599
+ const targetPath = follow ? await (0, import_promises2.realpath)(path24) : path24;
10597
11600
  if (this.fsw.closed)
10598
11601
  return;
10599
11602
  const parent = sp.dirname(wh.watchPath);
10600
11603
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10601
11604
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10602
- closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
11605
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
10603
11606
  if (this.fsw.closed)
10604
11607
  return;
10605
11608
  if (targetPath !== void 0) {
10606
- this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
11609
+ this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
10607
11610
  }
10608
11611
  } else {
10609
11612
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10610
11613
  }
10611
11614
  ready();
10612
11615
  if (closer)
10613
- this.fsw._addPathCloser(path23, closer);
11616
+ this.fsw._addPathCloser(path24, closer);
10614
11617
  return false;
10615
11618
  } catch (error) {
10616
11619
  if (this.fsw._handleError(error)) {
10617
11620
  ready();
10618
- return path23;
11621
+ return path24;
10619
11622
  }
10620
11623
  }
10621
11624
  }
@@ -10658,24 +11661,24 @@ function createPattern(matcher) {
10658
11661
  }
10659
11662
  return () => false;
10660
11663
  }
10661
- function normalizePath(path23) {
10662
- if (typeof path23 !== "string")
11664
+ function normalizePath(path24) {
11665
+ if (typeof path24 !== "string")
10663
11666
  throw new Error("string expected");
10664
- path23 = sp2.normalize(path23);
10665
- path23 = path23.replace(/\\/g, "/");
11667
+ path24 = sp2.normalize(path24);
11668
+ path24 = path24.replace(/\\/g, "/");
10666
11669
  let prepend = false;
10667
- if (path23.startsWith("//"))
11670
+ if (path24.startsWith("//"))
10668
11671
  prepend = true;
10669
- path23 = path23.replace(DOUBLE_SLASH_RE, "/");
11672
+ path24 = path24.replace(DOUBLE_SLASH_RE, "/");
10670
11673
  if (prepend)
10671
- path23 = "/" + path23;
10672
- return path23;
11674
+ path24 = "/" + path24;
11675
+ return path24;
10673
11676
  }
10674
11677
  function matchPatterns(patterns, testString, stats) {
10675
- const path23 = normalizePath(testString);
11678
+ const path24 = normalizePath(testString);
10676
11679
  for (let index = 0; index < patterns.length; index++) {
10677
11680
  const pattern = patterns[index];
10678
- if (pattern(path23, stats)) {
11681
+ if (pattern(path24, stats)) {
10679
11682
  return true;
10680
11683
  }
10681
11684
  }
@@ -10713,19 +11716,19 @@ var toUnix = (string) => {
10713
11716
  }
10714
11717
  return str;
10715
11718
  };
10716
- var normalizePathToUnix = (path23) => toUnix(sp2.normalize(toUnix(path23)));
10717
- var normalizeIgnored = (cwd = "") => (path23) => {
10718
- if (typeof path23 === "string") {
10719
- return normalizePathToUnix(sp2.isAbsolute(path23) ? path23 : sp2.join(cwd, path23));
11719
+ var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
11720
+ var normalizeIgnored = (cwd = "") => (path24) => {
11721
+ if (typeof path24 === "string") {
11722
+ return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
10720
11723
  } else {
10721
- return path23;
11724
+ return path24;
10722
11725
  }
10723
11726
  };
10724
- var getAbsolutePath = (path23, cwd) => {
10725
- if (sp2.isAbsolute(path23)) {
10726
- return path23;
11727
+ var getAbsolutePath = (path24, cwd) => {
11728
+ if (sp2.isAbsolute(path24)) {
11729
+ return path24;
10727
11730
  }
10728
- return sp2.join(cwd, path23);
11731
+ return sp2.join(cwd, path24);
10729
11732
  };
10730
11733
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10731
11734
  var DirEntry = class {
@@ -10790,10 +11793,10 @@ var WatchHelper = class {
10790
11793
  dirParts;
10791
11794
  followSymlinks;
10792
11795
  statMethod;
10793
- constructor(path23, follow, fsw) {
11796
+ constructor(path24, follow, fsw) {
10794
11797
  this.fsw = fsw;
10795
- const watchPath = path23;
10796
- this.path = path23 = path23.replace(REPLACER_RE, "");
11798
+ const watchPath = path24;
11799
+ this.path = path24 = path24.replace(REPLACER_RE, "");
10797
11800
  this.watchPath = watchPath;
10798
11801
  this.fullWatchPath = sp2.resolve(watchPath);
10799
11802
  this.dirParts = [];
@@ -10933,20 +11936,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10933
11936
  this._closePromise = void 0;
10934
11937
  let paths = unifyPaths(paths_);
10935
11938
  if (cwd) {
10936
- paths = paths.map((path23) => {
10937
- const absPath = getAbsolutePath(path23, cwd);
11939
+ paths = paths.map((path24) => {
11940
+ const absPath = getAbsolutePath(path24, cwd);
10938
11941
  return absPath;
10939
11942
  });
10940
11943
  }
10941
- paths.forEach((path23) => {
10942
- this._removeIgnoredPath(path23);
11944
+ paths.forEach((path24) => {
11945
+ this._removeIgnoredPath(path24);
10943
11946
  });
10944
11947
  this._userIgnored = void 0;
10945
11948
  if (!this._readyCount)
10946
11949
  this._readyCount = 0;
10947
11950
  this._readyCount += paths.length;
10948
- Promise.all(paths.map(async (path23) => {
10949
- const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
11951
+ Promise.all(paths.map(async (path24) => {
11952
+ const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
10950
11953
  if (res)
10951
11954
  this._emitReady();
10952
11955
  return res;
@@ -10968,17 +11971,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10968
11971
  return this;
10969
11972
  const paths = unifyPaths(paths_);
10970
11973
  const { cwd } = this.options;
10971
- paths.forEach((path23) => {
10972
- if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
11974
+ paths.forEach((path24) => {
11975
+ if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
10973
11976
  if (cwd)
10974
- path23 = sp2.join(cwd, path23);
10975
- path23 = sp2.resolve(path23);
11977
+ path24 = sp2.join(cwd, path24);
11978
+ path24 = sp2.resolve(path24);
10976
11979
  }
10977
- this._closePath(path23);
10978
- this._addIgnoredPath(path23);
10979
- if (this._watched.has(path23)) {
11980
+ this._closePath(path24);
11981
+ this._addIgnoredPath(path24);
11982
+ if (this._watched.has(path24)) {
10980
11983
  this._addIgnoredPath({
10981
- path: path23,
11984
+ path: path24,
10982
11985
  recursive: true
10983
11986
  });
10984
11987
  }
@@ -11042,38 +12045,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11042
12045
  * @param stats arguments to be passed with event
11043
12046
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
11044
12047
  */
11045
- async _emit(event, path23, stats) {
12048
+ async _emit(event, path24, stats) {
11046
12049
  if (this.closed)
11047
12050
  return;
11048
12051
  const opts = this.options;
11049
12052
  if (isWindows)
11050
- path23 = sp2.normalize(path23);
12053
+ path24 = sp2.normalize(path24);
11051
12054
  if (opts.cwd)
11052
- path23 = sp2.relative(opts.cwd, path23);
11053
- const args = [path23];
12055
+ path24 = sp2.relative(opts.cwd, path24);
12056
+ const args = [path24];
11054
12057
  if (stats != null)
11055
12058
  args.push(stats);
11056
12059
  const awf = opts.awaitWriteFinish;
11057
12060
  let pw;
11058
- if (awf && (pw = this._pendingWrites.get(path23))) {
12061
+ if (awf && (pw = this._pendingWrites.get(path24))) {
11059
12062
  pw.lastChange = /* @__PURE__ */ new Date();
11060
12063
  return this;
11061
12064
  }
11062
12065
  if (opts.atomic) {
11063
12066
  if (event === EVENTS.UNLINK) {
11064
- this._pendingUnlinks.set(path23, [event, ...args]);
12067
+ this._pendingUnlinks.set(path24, [event, ...args]);
11065
12068
  setTimeout(() => {
11066
- this._pendingUnlinks.forEach((entry, path24) => {
12069
+ this._pendingUnlinks.forEach((entry, path25) => {
11067
12070
  this.emit(...entry);
11068
12071
  this.emit(EVENTS.ALL, ...entry);
11069
- this._pendingUnlinks.delete(path24);
12072
+ this._pendingUnlinks.delete(path25);
11070
12073
  });
11071
12074
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
11072
12075
  return this;
11073
12076
  }
11074
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
12077
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
11075
12078
  event = EVENTS.CHANGE;
11076
- this._pendingUnlinks.delete(path23);
12079
+ this._pendingUnlinks.delete(path24);
11077
12080
  }
11078
12081
  }
11079
12082
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -11091,16 +12094,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11091
12094
  this.emitWithAll(event, args);
11092
12095
  }
11093
12096
  };
11094
- this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
12097
+ this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
11095
12098
  return this;
11096
12099
  }
11097
12100
  if (event === EVENTS.CHANGE) {
11098
- const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
12101
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
11099
12102
  if (isThrottled)
11100
12103
  return this;
11101
12104
  }
11102
12105
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
11103
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path23) : path23;
12106
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
11104
12107
  let stats2;
11105
12108
  try {
11106
12109
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -11131,23 +12134,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11131
12134
  * @param timeout duration of time to suppress duplicate actions
11132
12135
  * @returns tracking object or false if action should be suppressed
11133
12136
  */
11134
- _throttle(actionType, path23, timeout) {
12137
+ _throttle(actionType, path24, timeout) {
11135
12138
  if (!this._throttled.has(actionType)) {
11136
12139
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
11137
12140
  }
11138
12141
  const action = this._throttled.get(actionType);
11139
12142
  if (!action)
11140
12143
  throw new Error("invalid throttle");
11141
- const actionPath = action.get(path23);
12144
+ const actionPath = action.get(path24);
11142
12145
  if (actionPath) {
11143
12146
  actionPath.count++;
11144
12147
  return false;
11145
12148
  }
11146
12149
  let timeoutObject;
11147
12150
  const clear = () => {
11148
- const item = action.get(path23);
12151
+ const item = action.get(path24);
11149
12152
  const count = item ? item.count : 0;
11150
- action.delete(path23);
12153
+ action.delete(path24);
11151
12154
  clearTimeout(timeoutObject);
11152
12155
  if (item)
11153
12156
  clearTimeout(item.timeoutObject);
@@ -11155,7 +12158,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11155
12158
  };
11156
12159
  timeoutObject = setTimeout(clear, timeout);
11157
12160
  const thr = { timeoutObject, clear, count: 0 };
11158
- action.set(path23, thr);
12161
+ action.set(path24, thr);
11159
12162
  return thr;
11160
12163
  }
11161
12164
  _incrReadyCount() {
@@ -11169,44 +12172,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11169
12172
  * @param event
11170
12173
  * @param awfEmit Callback to be called when ready for event to be emitted.
11171
12174
  */
11172
- _awaitWriteFinish(path23, threshold, event, awfEmit) {
12175
+ _awaitWriteFinish(path24, threshold, event, awfEmit) {
11173
12176
  const awf = this.options.awaitWriteFinish;
11174
12177
  if (typeof awf !== "object")
11175
12178
  return;
11176
12179
  const pollInterval = awf.pollInterval;
11177
12180
  let timeoutHandler;
11178
- let fullPath = path23;
11179
- if (this.options.cwd && !sp2.isAbsolute(path23)) {
11180
- fullPath = sp2.join(this.options.cwd, path23);
12181
+ let fullPath = path24;
12182
+ if (this.options.cwd && !sp2.isAbsolute(path24)) {
12183
+ fullPath = sp2.join(this.options.cwd, path24);
11181
12184
  }
11182
12185
  const now = /* @__PURE__ */ new Date();
11183
12186
  const writes = this._pendingWrites;
11184
12187
  function awaitWriteFinishFn(prevStat) {
11185
12188
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
11186
- if (err || !writes.has(path23)) {
12189
+ if (err || !writes.has(path24)) {
11187
12190
  if (err && err.code !== "ENOENT")
11188
12191
  awfEmit(err);
11189
12192
  return;
11190
12193
  }
11191
12194
  const now2 = Number(/* @__PURE__ */ new Date());
11192
12195
  if (prevStat && curStat.size !== prevStat.size) {
11193
- writes.get(path23).lastChange = now2;
12196
+ writes.get(path24).lastChange = now2;
11194
12197
  }
11195
- const pw = writes.get(path23);
12198
+ const pw = writes.get(path24);
11196
12199
  const df = now2 - pw.lastChange;
11197
12200
  if (df >= threshold) {
11198
- writes.delete(path23);
12201
+ writes.delete(path24);
11199
12202
  awfEmit(void 0, curStat);
11200
12203
  } else {
11201
12204
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
11202
12205
  }
11203
12206
  });
11204
12207
  }
11205
- if (!writes.has(path23)) {
11206
- writes.set(path23, {
12208
+ if (!writes.has(path24)) {
12209
+ writes.set(path24, {
11207
12210
  lastChange: now,
11208
12211
  cancelWait: () => {
11209
- writes.delete(path23);
12212
+ writes.delete(path24);
11210
12213
  clearTimeout(timeoutHandler);
11211
12214
  return event;
11212
12215
  }
@@ -11217,8 +12220,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11217
12220
  /**
11218
12221
  * Determines whether user has asked to ignore this path.
11219
12222
  */
11220
- _isIgnored(path23, stats) {
11221
- if (this.options.atomic && DOT_RE.test(path23))
12223
+ _isIgnored(path24, stats) {
12224
+ if (this.options.atomic && DOT_RE.test(path24))
11222
12225
  return true;
11223
12226
  if (!this._userIgnored) {
11224
12227
  const { cwd } = this.options;
@@ -11228,17 +12231,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11228
12231
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11229
12232
  this._userIgnored = anymatch(list, void 0);
11230
12233
  }
11231
- return this._userIgnored(path23, stats);
12234
+ return this._userIgnored(path24, stats);
11232
12235
  }
11233
- _isntIgnored(path23, stat4) {
11234
- return !this._isIgnored(path23, stat4);
12236
+ _isntIgnored(path24, stat4) {
12237
+ return !this._isIgnored(path24, stat4);
11235
12238
  }
11236
12239
  /**
11237
12240
  * Provides a set of common helpers and properties relating to symlink handling.
11238
12241
  * @param path file or directory pattern being watched
11239
12242
  */
11240
- _getWatchHelpers(path23) {
11241
- return new WatchHelper(path23, this.options.followSymlinks, this);
12243
+ _getWatchHelpers(path24) {
12244
+ return new WatchHelper(path24, this.options.followSymlinks, this);
11242
12245
  }
11243
12246
  // Directory helpers
11244
12247
  // -----------------
@@ -11270,63 +12273,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11270
12273
  * @param item base path of item/directory
11271
12274
  */
11272
12275
  _remove(directory, item, isDirectory) {
11273
- const path23 = sp2.join(directory, item);
11274
- const fullPath = sp2.resolve(path23);
11275
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
11276
- if (!this._throttle("remove", path23, 100))
12276
+ const path24 = sp2.join(directory, item);
12277
+ const fullPath = sp2.resolve(path24);
12278
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
12279
+ if (!this._throttle("remove", path24, 100))
11277
12280
  return;
11278
12281
  if (!isDirectory && this._watched.size === 1) {
11279
12282
  this.add(directory, item, true);
11280
12283
  }
11281
- const wp = this._getWatchedDir(path23);
12284
+ const wp = this._getWatchedDir(path24);
11282
12285
  const nestedDirectoryChildren = wp.getChildren();
11283
- nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
12286
+ nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
11284
12287
  const parent = this._getWatchedDir(directory);
11285
12288
  const wasTracked = parent.has(item);
11286
12289
  parent.remove(item);
11287
12290
  if (this._symlinkPaths.has(fullPath)) {
11288
12291
  this._symlinkPaths.delete(fullPath);
11289
12292
  }
11290
- let relPath = path23;
12293
+ let relPath = path24;
11291
12294
  if (this.options.cwd)
11292
- relPath = sp2.relative(this.options.cwd, path23);
12295
+ relPath = sp2.relative(this.options.cwd, path24);
11293
12296
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11294
12297
  const event = this._pendingWrites.get(relPath).cancelWait();
11295
12298
  if (event === EVENTS.ADD)
11296
12299
  return;
11297
12300
  }
11298
- this._watched.delete(path23);
12301
+ this._watched.delete(path24);
11299
12302
  this._watched.delete(fullPath);
11300
12303
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11301
- if (wasTracked && !this._isIgnored(path23))
11302
- this._emit(eventName, path23);
11303
- this._closePath(path23);
12304
+ if (wasTracked && !this._isIgnored(path24))
12305
+ this._emit(eventName, path24);
12306
+ this._closePath(path24);
11304
12307
  }
11305
12308
  /**
11306
12309
  * Closes all watchers for a path
11307
12310
  */
11308
- _closePath(path23) {
11309
- this._closeFile(path23);
11310
- const dir = sp2.dirname(path23);
11311
- this._getWatchedDir(dir).remove(sp2.basename(path23));
12311
+ _closePath(path24) {
12312
+ this._closeFile(path24);
12313
+ const dir = sp2.dirname(path24);
12314
+ this._getWatchedDir(dir).remove(sp2.basename(path24));
11312
12315
  }
11313
12316
  /**
11314
12317
  * Closes only file-specific watchers
11315
12318
  */
11316
- _closeFile(path23) {
11317
- const closers = this._closers.get(path23);
12319
+ _closeFile(path24) {
12320
+ const closers = this._closers.get(path24);
11318
12321
  if (!closers)
11319
12322
  return;
11320
12323
  closers.forEach((closer) => closer());
11321
- this._closers.delete(path23);
12324
+ this._closers.delete(path24);
11322
12325
  }
11323
- _addPathCloser(path23, closer) {
12326
+ _addPathCloser(path24, closer) {
11324
12327
  if (!closer)
11325
12328
  return;
11326
- let list = this._closers.get(path23);
12329
+ let list = this._closers.get(path24);
11327
12330
  if (!list) {
11328
12331
  list = [];
11329
- this._closers.set(path23, list);
12332
+ this._closers.set(path24, list);
11330
12333
  }
11331
12334
  list.push(closer);
11332
12335
  }
@@ -11356,7 +12359,7 @@ function watch(paths, options = {}) {
11356
12359
  var chokidar_default = { watch, FSWatcher };
11357
12360
 
11358
12361
  // src/watcher/file-watcher.ts
11359
- var path16 = __toESM(require("path"), 1);
12362
+ var path17 = __toESM(require("path"), 1);
11360
12363
  var FileWatcher = class {
11361
12364
  watcher = null;
11362
12365
  projectRoot;
@@ -11367,6 +12370,8 @@ var FileWatcher = class {
11367
12370
  debounceTimer = null;
11368
12371
  debounceMs = 1e3;
11369
12372
  onChanges = null;
12373
+ readyPromise = null;
12374
+ resolveReady = null;
11370
12375
  constructor(projectRoot, config, host = "opencode", options = {}) {
11371
12376
  this.projectRoot = projectRoot;
11372
12377
  this.config = config;
@@ -11382,15 +12387,15 @@ var FileWatcher = class {
11382
12387
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
11383
12388
  this.watcher = chokidar_default.watch(watchTargets, {
11384
12389
  ignored: (filePath) => {
11385
- const relativePath = path16.relative(this.projectRoot, filePath);
12390
+ const relativePath = path17.relative(this.projectRoot, filePath);
11386
12391
  if (!relativePath) return false;
11387
12392
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
11388
12393
  return false;
11389
12394
  }
11390
- if (hasFilteredPathSegment(relativePath, path16.sep)) {
12395
+ if (hasFilteredPathSegment(relativePath, path17.sep)) {
11391
12396
  return true;
11392
12397
  }
11393
- if (isRestrictedDirectory(relativePath, path16.sep)) {
12398
+ if (isRestrictedDirectory(relativePath, path17.sep)) {
11394
12399
  return true;
11395
12400
  }
11396
12401
  if (ignoreFilter.ignores(relativePath)) {
@@ -11405,6 +12410,13 @@ var FileWatcher = class {
11405
12410
  pollInterval: 100
11406
12411
  }
11407
12412
  });
12413
+ this.readyPromise = new Promise((resolve13) => {
12414
+ this.resolveReady = resolve13;
12415
+ });
12416
+ this.watcher.once("ready", () => {
12417
+ this.resolveReady?.();
12418
+ this.resolveReady = null;
12419
+ });
11408
12420
  this.watcher.on("error", (error) => {
11409
12421
  const err = error instanceof Error ? error : null;
11410
12422
  if (err?.code === "EPERM" || err?.code === "EACCES") {
@@ -11436,24 +12448,24 @@ var FileWatcher = class {
11436
12448
  this.scheduleFlush();
11437
12449
  }
11438
12450
  isProjectConfigPath(filePath) {
11439
- const relativePath = path16.relative(this.projectRoot, filePath);
11440
- const normalizedRelativePath = path16.normalize(relativePath);
12451
+ const relativePath = path17.relative(this.projectRoot, filePath);
12452
+ const normalizedRelativePath = path17.normalize(relativePath);
11441
12453
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
11442
12454
  }
11443
12455
  isProjectConfigPathOrAncestor(relativePath) {
11444
- const normalizedRelativePath = path16.normalize(relativePath);
12456
+ const normalizedRelativePath = path17.normalize(relativePath);
11445
12457
  return this.getProjectConfigRelativePaths().some(
11446
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path16.sep}`)
12458
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path17.sep}`)
11447
12459
  );
11448
12460
  }
11449
12461
  getProjectConfigRelativePaths() {
11450
12462
  if (this.configPath) {
11451
- return [path16.normalize(path16.relative(this.projectRoot, this.configPath))];
12463
+ return [path17.normalize(path17.relative(this.projectRoot, this.configPath))];
11452
12464
  }
11453
12465
  return [
11454
12466
  resolveProjectConfigPath(this.projectRoot, this.host),
11455
12467
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
11456
- ].map((configPath) => path16.normalize(path16.relative(this.projectRoot, configPath)));
12468
+ ].map((configPath) => path17.normalize(path17.relative(this.projectRoot, configPath)));
11457
12469
  }
11458
12470
  scheduleFlush() {
11459
12471
  if (this.debounceTimer) {
@@ -11468,7 +12480,7 @@ var FileWatcher = class {
11468
12480
  return;
11469
12481
  }
11470
12482
  const changes = Array.from(this.pendingChanges.entries()).map(
11471
- ([path23, type]) => ({ path: path23, type })
12483
+ ([path24, type]) => ({ path: path24, type })
11472
12484
  );
11473
12485
  this.pendingChanges.clear();
11474
12486
  try {
@@ -11477,25 +12489,32 @@ var FileWatcher = class {
11477
12489
  console.error("Error handling file changes:", error);
11478
12490
  }
11479
12491
  }
11480
- stop() {
12492
+ async stop() {
11481
12493
  if (this.debounceTimer) {
11482
12494
  clearTimeout(this.debounceTimer);
11483
12495
  this.debounceTimer = null;
11484
12496
  }
11485
12497
  if (this.watcher) {
11486
- this.watcher.close();
12498
+ const watcher = this.watcher;
11487
12499
  this.watcher = null;
12500
+ await watcher.close();
11488
12501
  }
12502
+ this.resolveReady?.();
12503
+ this.resolveReady = null;
12504
+ this.readyPromise = null;
11489
12505
  this.pendingChanges.clear();
11490
12506
  this.onChanges = null;
11491
12507
  }
11492
12508
  isRunning() {
11493
12509
  return this.watcher !== null;
11494
12510
  }
12511
+ async waitUntilReady() {
12512
+ await (this.readyPromise ?? Promise.resolve());
12513
+ }
11495
12514
  };
11496
12515
 
11497
12516
  // src/watcher/git-head-watcher.ts
11498
- var path17 = __toESM(require("path"), 1);
12517
+ var path18 = __toESM(require("path"), 1);
11499
12518
  var GitHeadWatcher = class {
11500
12519
  watcher = null;
11501
12520
  projectRoot;
@@ -11517,7 +12536,7 @@ var GitHeadWatcher = class {
11517
12536
  this.onBranchChange = handler;
11518
12537
  this.currentBranch = getCurrentBranch(this.projectRoot);
11519
12538
  const headPath = getHeadPath(this.projectRoot);
11520
- const refsPath = path17.join(this.projectRoot, ".git", "refs", "heads");
12539
+ const refsPath = path18.join(this.projectRoot, ".git", "refs", "heads");
11521
12540
  this.watcher = chokidar_default.watch([headPath, refsPath], {
11522
12541
  persistent: true,
11523
12542
  ignoreInitial: true,
@@ -11554,14 +12573,15 @@ var GitHeadWatcher = class {
11554
12573
  getCurrentBranch() {
11555
12574
  return this.currentBranch;
11556
12575
  }
11557
- stop() {
12576
+ async stop() {
11558
12577
  if (this.debounceTimer) {
11559
12578
  clearTimeout(this.debounceTimer);
11560
12579
  this.debounceTimer = null;
11561
12580
  }
11562
12581
  if (this.watcher) {
11563
- this.watcher.close();
12582
+ const watcher = this.watcher;
11564
12583
  this.watcher = null;
12584
+ await watcher.close();
11565
12585
  }
11566
12586
  this.onBranchChange = null;
11567
12587
  }
@@ -11579,6 +12599,8 @@ var BackgroundReindexer = class {
11579
12599
  running = false;
11580
12600
  pending = false;
11581
12601
  stopped = false;
12602
+ retryTimer = null;
12603
+ retryDelayMs = 50;
11582
12604
  request() {
11583
12605
  if (this.stopped) {
11584
12606
  return;
@@ -11589,9 +12611,13 @@ var BackgroundReindexer = class {
11589
12611
  stop() {
11590
12612
  this.stopped = true;
11591
12613
  this.pending = false;
12614
+ if (this.retryTimer) {
12615
+ clearTimeout(this.retryTimer);
12616
+ this.retryTimer = null;
12617
+ }
11592
12618
  }
11593
12619
  drain() {
11594
- if (this.stopped || this.running || !this.pending) {
12620
+ if (this.stopped || this.running || this.retryTimer || !this.pending) {
11595
12621
  return;
11596
12622
  }
11597
12623
  this.pending = false;
@@ -11599,13 +12625,29 @@ var BackgroundReindexer = class {
11599
12625
  void this.run();
11600
12626
  }
11601
12627
  async run() {
12628
+ let retryAfterContention = false;
11602
12629
  try {
11603
12630
  await this.runIndex();
12631
+ this.retryDelayMs = 50;
11604
12632
  } catch (error) {
11605
- console.error("[codebase-index] Background reindex failed:", error);
12633
+ if (isTransientIndexLockContention(error)) {
12634
+ this.pending = true;
12635
+ retryAfterContention = true;
12636
+ } else {
12637
+ console.error("[codebase-index] Background reindex failed:", error);
12638
+ }
11606
12639
  } finally {
11607
12640
  this.running = false;
11608
- this.drain();
12641
+ if (retryAfterContention && !this.stopped) {
12642
+ const delay = this.retryDelayMs;
12643
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
12644
+ this.retryTimer = setTimeout(() => {
12645
+ this.retryTimer = null;
12646
+ this.drain();
12647
+ }, delay);
12648
+ } else {
12649
+ this.drain();
12650
+ }
11609
12651
  }
11610
12652
  }
11611
12653
  };
@@ -11639,10 +12681,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
11639
12681
  return {
11640
12682
  fileWatcher,
11641
12683
  gitWatcher,
11642
- stop() {
12684
+ whenReady() {
12685
+ return fileWatcher.waitUntilReady();
12686
+ },
12687
+ async stop() {
11643
12688
  backgroundReindexer.stop();
11644
- fileWatcher.stop();
11645
- gitWatcher?.stop();
12689
+ await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
11646
12690
  }
11647
12691
  };
11648
12692
  }
@@ -11748,13 +12792,13 @@ var pr_impact = (0, import_plugin.tool)({
11748
12792
  });
11749
12793
 
11750
12794
  // src/tools/index.ts
11751
- var import_fs10 = require("fs");
11752
- var os4 = __toESM(require("os"), 1);
11753
- var path20 = __toESM(require("path"), 1);
12795
+ var import_fs11 = require("fs");
12796
+ var os5 = __toESM(require("os"), 1);
12797
+ var path21 = __toESM(require("path"), 1);
11754
12798
 
11755
12799
  // src/tools/visualize/activity.ts
11756
12800
  var import_child_process4 = require("child_process");
11757
- var path18 = __toESM(require("path"), 1);
12801
+ var path19 = __toESM(require("path"), 1);
11758
12802
  function attachRecentActivity(data, projectRoot) {
11759
12803
  const activity = readGitActivity(projectRoot);
11760
12804
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -11916,7 +12960,7 @@ function normalizePath2(filePath) {
11916
12960
  return filePath.replace(/\\/g, "/");
11917
12961
  }
11918
12962
  function toGitRelativePath(projectRoot, filePath) {
11919
- const relativePath = path18.isAbsolute(filePath) ? path18.relative(projectRoot, filePath) : filePath;
12963
+ const relativePath = path19.isAbsolute(filePath) ? path19.relative(projectRoot, filePath) : filePath;
11920
12964
  return normalizePath2(relativePath);
11921
12965
  }
11922
12966
 
@@ -12174,7 +13218,7 @@ render();
12174
13218
  }
12175
13219
 
12176
13220
  // src/tools/visualize/transform.ts
12177
- var path19 = __toESM(require("path"), 1);
13221
+ var path20 = __toESM(require("path"), 1);
12178
13222
 
12179
13223
  // src/tools/visualize/modules.ts
12180
13224
  var MAX_MODULES = 18;
@@ -12434,7 +13478,7 @@ function transformForVisualization(symbols, edges, options = {}) {
12434
13478
  filePath: s.filePath,
12435
13479
  kind: s.kind,
12436
13480
  line: s.startLine,
12437
- directory: path19.dirname(s.filePath),
13481
+ directory: path20.dirname(s.filePath),
12438
13482
  moduleId: "",
12439
13483
  moduleLabel: ""
12440
13484
  }));
@@ -12515,7 +13559,9 @@ var index_codebase = (0, import_plugin2.tool)({
12515
13559
  const result = await runIndexCodebase(context?.worktree, DEFAULT_HOST, args, (title, metadata) => {
12516
13560
  context.metadata({ title, metadata });
12517
13561
  });
12518
- return result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
13562
+ if (result.kind === "estimate") return formatCostEstimate(result.estimate);
13563
+ if (result.kind === "busy") return result.text;
13564
+ return formatIndexStats(result.stats, args.verbose ?? false);
12519
13565
  }
12520
13566
  });
12521
13567
  var index_status = (0, import_plugin2.tool)({
@@ -12529,7 +13575,9 @@ var index_health_check = (0, import_plugin2.tool)({
12529
13575
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
12530
13576
  args: {},
12531
13577
  async execute(_args, context) {
12532
- return formatHealthCheck(await getIndexHealthCheck(context?.worktree, DEFAULT_HOST));
13578
+ const result = await runIndexHealthCheck(context?.worktree, DEFAULT_HOST);
13579
+ if (result.kind === "busy") return result.text;
13580
+ return formatHealthCheck(result.health);
12533
13581
  }
12534
13582
  });
12535
13583
  var index_metrics = (0, import_plugin2.tool)({
@@ -12649,8 +13697,8 @@ var call_graph_path = (0, import_plugin2.tool)({
12649
13697
  maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12650
13698
  },
12651
13699
  async execute(args, context) {
12652
- const path23 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
12653
- return formatCallGraphPath(args.from, args.to, path23);
13700
+ const path24 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
13701
+ return formatCallGraphPath(args.from, args.to, path24);
12654
13702
  }
12655
13703
  });
12656
13704
  var add_knowledge_base = (0, import_plugin2.tool)({
@@ -12703,8 +13751,8 @@ var index_visualize = (0, import_plugin2.tool)({
12703
13751
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
12704
13752
  }
12705
13753
  const html = generateVisualizationHtml(vizData);
12706
- const outputPath = path20.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12707
- (0, import_fs10.writeFileSync)(outputPath, html, "utf-8");
13754
+ const outputPath = path21.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
13755
+ (0, import_fs11.writeFileSync)(outputPath, html, "utf-8");
12708
13756
  let result = `Temporal call graph visualization generated: ${outputPath}
12709
13757
 
12710
13758
  `;
@@ -12725,8 +13773,8 @@ var index_visualize = (0, import_plugin2.tool)({
12725
13773
  });
12726
13774
 
12727
13775
  // src/commands/loader.ts
12728
- var import_fs11 = require("fs");
12729
- var path21 = __toESM(require("path"), 1);
13776
+ var import_fs12 = require("fs");
13777
+ var path22 = __toESM(require("path"), 1);
12730
13778
  function parseFrontmatter(content) {
12731
13779
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12732
13780
  const match = content.match(frontmatterRegex);
@@ -12747,21 +13795,21 @@ function parseFrontmatter(content) {
12747
13795
  }
12748
13796
  function loadCommandsFromDirectory(commandsDir) {
12749
13797
  const commands = /* @__PURE__ */ new Map();
12750
- if (!(0, import_fs11.existsSync)(commandsDir)) {
13798
+ if (!(0, import_fs12.existsSync)(commandsDir)) {
12751
13799
  return commands;
12752
13800
  }
12753
- const files = (0, import_fs11.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
13801
+ const files = (0, import_fs12.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
12754
13802
  for (const file of files) {
12755
- const filePath = path21.join(commandsDir, file);
13803
+ const filePath = path22.join(commandsDir, file);
12756
13804
  let content;
12757
13805
  try {
12758
- content = (0, import_fs11.readFileSync)(filePath, "utf-8");
13806
+ content = (0, import_fs12.readFileSync)(filePath, "utf-8");
12759
13807
  } catch (error) {
12760
13808
  const message = error instanceof Error ? error.message : String(error);
12761
13809
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
12762
13810
  }
12763
13811
  const { frontmatter, body } = parseFrontmatter(content);
12764
- const name = path21.basename(file, ".md");
13812
+ const name = path22.basename(file, ".md");
12765
13813
  const description = frontmatter.description || `Run the ${name} command`;
12766
13814
  commands.set(name, {
12767
13815
  description,
@@ -13065,18 +14113,37 @@ var RoutingHintController = class {
13065
14113
  };
13066
14114
 
13067
14115
  // src/utils/auto-index.ts
14116
+ var INITIAL_RETRY_DELAY_MS = 50;
14117
+ var MAX_RETRY_DELAY_MS = 500;
14118
+ var autoIndexStates = /* @__PURE__ */ new WeakMap();
13068
14119
  function getErrorMessage3(error) {
13069
14120
  return error instanceof Error ? error.message : String(error);
13070
14121
  }
13071
- function startAutoIndex(indexer, projectRoot) {
13072
- indexer.initialize().then(() => {
13073
- indexer.index().catch((error) => {
13074
- console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
13075
- });
14122
+ function runAutoIndex(indexer, projectRoot, state) {
14123
+ void indexer.index().then(() => {
14124
+ autoIndexStates.delete(indexer);
13076
14125
  }).catch((error) => {
13077
- console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14126
+ if (!isTransientIndexLockContention(error)) {
14127
+ autoIndexStates.delete(indexer);
14128
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14129
+ return;
14130
+ }
14131
+ const retryDelayMs = state.retryDelayMs;
14132
+ state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
14133
+ const retryTimer = setTimeout(() => {
14134
+ runAutoIndex(indexer, projectRoot, state);
14135
+ }, retryDelayMs);
14136
+ retryTimer.unref?.();
13078
14137
  });
13079
14138
  }
14139
+ function startAutoIndex(indexer, projectRoot) {
14140
+ if (autoIndexStates.has(indexer)) return;
14141
+ const state = {
14142
+ retryDelayMs: INITIAL_RETRY_DELAY_MS
14143
+ };
14144
+ autoIndexStates.set(indexer, state);
14145
+ runAutoIndex(indexer, projectRoot, state);
14146
+ }
13080
14147
 
13081
14148
  // src/index.ts
13082
14149
  var import_meta2 = {};
@@ -13094,9 +14161,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
13094
14161
  function getCommandsDir() {
13095
14162
  let currentDir = process.cwd();
13096
14163
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
13097
- currentDir = path22.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
14164
+ currentDir = path23.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
13098
14165
  }
13099
- return path22.join(currentDir, "..", "commands");
14166
+ return path23.join(currentDir, "..", "commands");
13100
14167
  }
13101
14168
  function appendRoutingHints(output, hints, preferredRole) {
13102
14169
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -13116,7 +14183,7 @@ var plugin = async ({ directory, worktree }) => {
13116
14183
  initializeTools2(projectRoot, config);
13117
14184
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
13118
14185
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
13119
- const isHomeDir = path22.resolve(projectRoot) === path22.resolve(os5.homedir());
14186
+ const isHomeDir = path23.resolve(projectRoot) === path23.resolve(os6.homedir());
13120
14187
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
13121
14188
  if (isHomeDir) {
13122
14189
  console.warn(