opencode-codebase-index 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -490,7 +490,7 @@ var require_ignore = __commonJS({
490
490
  // path matching.
491
491
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
492
492
  // @returns {TestResult} true if a file is ignored
493
- test(path23, checkUnignored, mode) {
493
+ test(path24, checkUnignored, mode) {
494
494
  let ignored = false;
495
495
  let unignored = false;
496
496
  let matchedRule;
@@ -499,7 +499,7 @@ var require_ignore = __commonJS({
499
499
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
500
500
  return;
501
501
  }
502
- const matched = rule[mode].test(path23);
502
+ const matched = rule[mode].test(path24);
503
503
  if (!matched) {
504
504
  return;
505
505
  }
@@ -520,17 +520,17 @@ var require_ignore = __commonJS({
520
520
  var throwError = (message, Ctor) => {
521
521
  throw new Ctor(message);
522
522
  };
523
- var checkPath = (path23, originalPath, doThrow) => {
524
- if (!isString(path23)) {
523
+ var checkPath = (path24, originalPath, doThrow) => {
524
+ if (!isString(path24)) {
525
525
  return doThrow(
526
526
  `path must be a string, but got \`${originalPath}\``,
527
527
  TypeError
528
528
  );
529
529
  }
530
- if (!path23) {
530
+ if (!path24) {
531
531
  return doThrow(`path must not be empty`, TypeError);
532
532
  }
533
- if (checkPath.isNotRelative(path23)) {
533
+ if (checkPath.isNotRelative(path24)) {
534
534
  const r = "`path.relative()`d";
535
535
  return doThrow(
536
536
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -539,7 +539,7 @@ var require_ignore = __commonJS({
539
539
  }
540
540
  return true;
541
541
  };
542
- var isNotRelative = (path23) => REGEX_TEST_INVALID_PATH.test(path23);
542
+ var isNotRelative = (path24) => REGEX_TEST_INVALID_PATH.test(path24);
543
543
  checkPath.isNotRelative = isNotRelative;
544
544
  checkPath.convert = (p) => p;
545
545
  var Ignore2 = class {
@@ -569,19 +569,19 @@ var require_ignore = __commonJS({
569
569
  }
570
570
  // @returns {TestResult}
571
571
  _test(originalPath, cache, checkUnignored, slices) {
572
- const path23 = originalPath && checkPath.convert(originalPath);
572
+ const path24 = originalPath && checkPath.convert(originalPath);
573
573
  checkPath(
574
- path23,
574
+ path24,
575
575
  originalPath,
576
576
  this._strictPathCheck ? throwError : RETURN_FALSE
577
577
  );
578
- return this._t(path23, cache, checkUnignored, slices);
578
+ return this._t(path24, cache, checkUnignored, slices);
579
579
  }
580
- checkIgnore(path23) {
581
- if (!REGEX_TEST_TRAILING_SLASH.test(path23)) {
582
- return this.test(path23);
580
+ checkIgnore(path24) {
581
+ if (!REGEX_TEST_TRAILING_SLASH.test(path24)) {
582
+ return this.test(path24);
583
583
  }
584
- const slices = path23.split(SLASH2).filter(Boolean);
584
+ const slices = path24.split(SLASH2).filter(Boolean);
585
585
  slices.pop();
586
586
  if (slices.length) {
587
587
  const parent = this._t(
@@ -594,18 +594,18 @@ var require_ignore = __commonJS({
594
594
  return parent;
595
595
  }
596
596
  }
597
- return this._rules.test(path23, false, MODE_CHECK_IGNORE);
597
+ return this._rules.test(path24, false, MODE_CHECK_IGNORE);
598
598
  }
599
- _t(path23, cache, checkUnignored, slices) {
600
- if (path23 in cache) {
601
- return cache[path23];
599
+ _t(path24, cache, checkUnignored, slices) {
600
+ if (path24 in cache) {
601
+ return cache[path24];
602
602
  }
603
603
  if (!slices) {
604
- slices = path23.split(SLASH2).filter(Boolean);
604
+ slices = path24.split(SLASH2).filter(Boolean);
605
605
  }
606
606
  slices.pop();
607
607
  if (!slices.length) {
608
- return cache[path23] = this._rules.test(path23, checkUnignored, MODE_IGNORE);
608
+ return cache[path24] = this._rules.test(path24, checkUnignored, MODE_IGNORE);
609
609
  }
610
610
  const parent = this._t(
611
611
  slices.join(SLASH2) + SLASH2,
@@ -613,29 +613,29 @@ var require_ignore = __commonJS({
613
613
  checkUnignored,
614
614
  slices
615
615
  );
616
- return cache[path23] = parent.ignored ? parent : this._rules.test(path23, checkUnignored, MODE_IGNORE);
616
+ return cache[path24] = parent.ignored ? parent : this._rules.test(path24, checkUnignored, MODE_IGNORE);
617
617
  }
618
- ignores(path23) {
619
- return this._test(path23, this._ignoreCache, false).ignored;
618
+ ignores(path24) {
619
+ return this._test(path24, this._ignoreCache, false).ignored;
620
620
  }
621
621
  createFilter() {
622
- return (path23) => !this.ignores(path23);
622
+ return (path24) => !this.ignores(path24);
623
623
  }
624
624
  filter(paths) {
625
625
  return makeArray(paths).filter(this.createFilter());
626
626
  }
627
627
  // @returns {TestResult}
628
- test(path23) {
629
- return this._test(path23, this._testCache, true);
628
+ test(path24) {
629
+ return this._test(path24, this._testCache, true);
630
630
  }
631
631
  };
632
632
  var factory = (options) => new Ignore2(options);
633
- var isPathValid = (path23) => checkPath(path23 && checkPath.convert(path23), path23, RETURN_FALSE);
633
+ var isPathValid = (path24) => checkPath(path24 && checkPath.convert(path24), path24, RETURN_FALSE);
634
634
  var setupWindows = () => {
635
635
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
636
636
  checkPath.convert = makePosix;
637
637
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
638
- checkPath.isNotRelative = (path23) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path23) || isNotRelative(path23);
638
+ checkPath.isNotRelative = (path24) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path24) || isNotRelative(path24);
639
639
  };
640
640
  if (
641
641
  // Detect `process` so that it can run in browsers.
@@ -651,16 +651,16 @@ var require_ignore = __commonJS({
651
651
  });
652
652
 
653
653
  // src/index.ts
654
- import * as os5 from "os";
655
- import * as path22 from "path";
654
+ import * as os6 from "os";
655
+ import * as path23 from "path";
656
656
  import { fileURLToPath as fileURLToPath2 } from "url";
657
657
 
658
658
  // src/config/constants.ts
659
659
  var DEFAULT_INCLUDE = [
660
- "**/*.{ts,tsx,js,jsx,mjs,cjs}",
660
+ "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
661
661
  "**/*.{py,pyi}",
662
- "**/*.{go,rs,java,kt,scala}",
663
- "**/*.{c,cpp,cc,h,hpp}",
662
+ "**/*.{go,rs,java,cs,kt,scala}",
663
+ "**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
664
664
  "**/*.{rb,php,inc,swift}",
665
665
  "**/*.{cls,trigger}",
666
666
  "**/*.{vue,svelte,astro}",
@@ -1244,6 +1244,9 @@ function getHostProjectIndexRelativePath(host) {
1244
1244
  function hasHostProjectConfig(projectRoot, host) {
1245
1245
  return existsSync3(path3.join(projectRoot, getProjectConfigRelativePath(host)));
1246
1246
  }
1247
+ function hasHostGlobalConfig(host) {
1248
+ return existsSync3(getGlobalConfigPath(host));
1249
+ }
1247
1250
  function getGlobalIndexPath(host = "opencode") {
1248
1251
  switch (host) {
1249
1252
  case "opencode":
@@ -1283,6 +1286,9 @@ function resolveGlobalIndexPath(host = "opencode") {
1283
1286
  return hostIndexPath;
1284
1287
  }
1285
1288
  if (host !== "opencode") {
1289
+ if (hasHostGlobalConfig(host)) {
1290
+ return hostIndexPath;
1291
+ }
1286
1292
  const legacyIndexPath = getGlobalIndexPath("opencode");
1287
1293
  if (existsSync3(legacyIndexPath)) {
1288
1294
  return legacyIndexPath;
@@ -1581,13 +1587,439 @@ function loadMergedConfig(projectRoot, host = "opencode") {
1581
1587
  return merged;
1582
1588
  }
1583
1589
 
1590
+ // src/indexer/index-lock.ts
1591
+ import { randomUUID } from "crypto";
1592
+ import {
1593
+ existsSync as existsSync5,
1594
+ lstatSync,
1595
+ mkdirSync as mkdirSync2,
1596
+ readFileSync as readFileSync4,
1597
+ readdirSync as readdirSync2,
1598
+ realpathSync,
1599
+ renameSync,
1600
+ rmSync,
1601
+ writeFileSync as writeFileSync2
1602
+ } from "fs";
1603
+ import * as os2 from "os";
1604
+ import * as path7 from "path";
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(readFileSync4(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(readFileSync4(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}.${randomUUID()}`;
1711
+ mkdirSync2(candidatePath, { mode: 448 });
1712
+ try {
1713
+ writeFileSync2(path7.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
1714
+ encoding: "utf-8",
1715
+ flag: "wx",
1716
+ mode: 384
1717
+ });
1718
+ if (existsSync5(finalPath)) return false;
1719
+ try {
1720
+ renameSync(candidatePath, finalPath);
1721
+ return true;
1722
+ } catch (error) {
1723
+ if (existsSync5(finalPath)) return false;
1724
+ throw error;
1725
+ }
1726
+ } finally {
1727
+ if (existsSync5(candidatePath)) 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: 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 = readdirSync2(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 = 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 readdirSync2(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
+ 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}.${randomUUID()}`;
1805
+ try {
1806
+ 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 (!existsSync5(markerPath) && existsSync5(claimedMarkerPath)) {
1815
+ renameSync(claimedMarkerPath, markerPath);
1816
+ }
1817
+ return false;
1818
+ }
1819
+ 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: 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
+ renameSync(lockPath, quarantinePath);
1850
+ 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
+ mkdirSync2(indexPath, { recursive: true });
1880
+ const canonicalIndexPath = 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 = 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(() => 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 (!existsSync5(lease.lockPath) && existsSync5(releasePath)) {
1938
+ renameSync(releasePath, lease.lockPath);
1939
+ }
1940
+ return false;
1941
+ }
1942
+ try {
1943
+ retryTransientFilesystemOperation(() => 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 (existsSync5(temporaryPath)) 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 (!existsSync5(backupPath)) continue;
1995
+ if (existsSync5(targetPath)) rmSync(targetPath, { recursive: true, force: true });
1996
+ renameSync(backupPath, targetPath);
1997
+ }
1998
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
1999
+ for (const entry of readdirSync2(indexPath)) {
2000
+ if (!entry.includes(temporaryOwnerMarker)) continue;
2001
+ 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
+ rmSync(recovery.markerPath, { recursive: true, force: true });
2013
+ }
2014
+ }
2015
+
1584
2016
  // src/tools/operations.ts
1585
- import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
1586
- import * as path15 from "path";
2017
+ import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
2018
+ import * as path16 from "path";
1587
2019
 
1588
2020
  // src/indexer/index.ts
1589
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
1590
- import * as path12 from "path";
2021
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync3, promises as fsPromises2 } from "fs";
2022
+ import * as path13 from "path";
1591
2023
  import { performance as performance2 } from "perf_hooks";
1592
2024
  import { execFile as execFile3 } from "child_process";
1593
2025
  import { promisify as promisify3 } from "util";
@@ -2637,17 +3069,17 @@ async function pRetry(input, options = {}) {
2637
3069
  }
2638
3070
 
2639
3071
  // src/embeddings/detector.ts
2640
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2641
- import * as path7 from "path";
2642
- import * as os2 from "os";
3072
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3073
+ import * as path8 from "path";
3074
+ import * as os3 from "os";
2643
3075
  function getOpenCodeAuthPath() {
2644
- return path7.join(os2.homedir(), ".local", "share", "opencode", "auth.json");
3076
+ return path8.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
2645
3077
  }
2646
3078
  function loadOpenCodeAuth() {
2647
3079
  const authPath = getOpenCodeAuthPath();
2648
3080
  try {
2649
- if (existsSync5(authPath)) {
2650
- return JSON.parse(readFileSync4(authPath, "utf-8"));
3081
+ if (existsSync6(authPath)) {
3082
+ return JSON.parse(readFileSync5(authPath, "utf-8"));
2651
3083
  }
2652
3084
  } catch {
2653
3085
  }
@@ -2869,17 +3301,17 @@ function validateExternalUrl(urlString) {
2869
3301
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2870
3302
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2871
3303
  }
2872
- const hostname = parsed.hostname.toLowerCase();
2873
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2874
- 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})` };
2875
3307
  }
2876
3308
  for (const pattern of BLOCKED_METADATA_IPS) {
2877
- if (pattern.test(hostname)) {
2878
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
3309
+ if (pattern.test(hostname2)) {
3310
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2879
3311
  }
2880
3312
  }
2881
- if (/^169\.254\./.test(hostname)) {
2882
- 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})` };
2883
3315
  }
2884
3316
  return { valid: true };
2885
3317
  }
@@ -3350,8 +3782,8 @@ var SiliconFlowReranker = class {
3350
3782
 
3351
3783
  // src/utils/files.ts
3352
3784
  var import_ignore = __toESM(require_ignore(), 1);
3353
- import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
3354
- import * as path8 from "path";
3785
+ import { existsSync as existsSync7, readFileSync as readFileSync6, promises as fsPromises } from "fs";
3786
+ import * as path9 from "path";
3355
3787
  var PROJECT_MARKERS = [
3356
3788
  ".git",
3357
3789
  "package.json",
@@ -3371,7 +3803,7 @@ var PROJECT_MARKERS = [
3371
3803
  ];
3372
3804
  function hasProjectMarker(projectRoot) {
3373
3805
  for (const marker of PROJECT_MARKERS) {
3374
- if (existsSync6(path8.join(projectRoot, marker))) {
3806
+ if (existsSync7(path9.join(projectRoot, marker))) {
3375
3807
  return true;
3376
3808
  }
3377
3809
  }
@@ -3398,16 +3830,16 @@ function createIgnoreFilter(projectRoot) {
3398
3830
  "**/*build*/**"
3399
3831
  ];
3400
3832
  ig.add(defaultIgnores);
3401
- const gitignorePath = path8.join(projectRoot, ".gitignore");
3402
- if (existsSync6(gitignorePath)) {
3403
- const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
3833
+ const gitignorePath = path9.join(projectRoot, ".gitignore");
3834
+ if (existsSync7(gitignorePath)) {
3835
+ const gitignoreContent = readFileSync6(gitignorePath, "utf-8");
3404
3836
  ig.add(gitignoreContent);
3405
3837
  }
3406
3838
  return ig;
3407
3839
  }
3408
3840
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3409
- const relativePath = path8.relative(projectRoot, filePath);
3410
- if (hasFilteredPathSegment(relativePath, path8.sep)) {
3841
+ const relativePath = path9.relative(projectRoot, filePath);
3842
+ if (hasFilteredPathSegment(relativePath, path9.sep)) {
3411
3843
  return false;
3412
3844
  }
3413
3845
  if (ignoreFilter.ignores(relativePath)) {
@@ -3445,8 +3877,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3445
3877
  const filesInDir = [];
3446
3878
  const subdirs = [];
3447
3879
  for (const entry of entries) {
3448
- const fullPath = path8.join(dir, entry.name);
3449
- const relativePath = path8.relative(projectRoot, fullPath);
3880
+ const fullPath = path9.join(dir, entry.name);
3881
+ const relativePath = path9.relative(projectRoot, fullPath);
3450
3882
  if (isHiddenPathSegment(entry.name)) {
3451
3883
  if (entry.isDirectory()) {
3452
3884
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3495,7 +3927,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3495
3927
  yield f;
3496
3928
  }
3497
3929
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3498
- skipped.push({ path: path8.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3930
+ skipped.push({ path: path9.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3499
3931
  }
3500
3932
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3501
3933
  if (canRecurse) {
@@ -3535,8 +3967,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3535
3967
  if (additionalRoots && additionalRoots.length > 0) {
3536
3968
  const normalizedRoots = /* @__PURE__ */ new Set();
3537
3969
  for (const kbRoot of additionalRoots) {
3538
- const resolved = path8.normalize(
3539
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot, kbRoot)
3970
+ const resolved = path9.normalize(
3971
+ path9.isAbsolute(kbRoot) ? kbRoot : path9.resolve(projectRoot, kbRoot)
3540
3972
  );
3541
3973
  normalizedRoots.add(resolved);
3542
3974
  }
@@ -3943,13 +4375,13 @@ function initializeLogger(config) {
3943
4375
  }
3944
4376
 
3945
4377
  // src/native/index.ts
3946
- import * as path9 from "path";
3947
- import * as os3 from "os";
4378
+ import * as path10 from "path";
4379
+ import * as os4 from "os";
3948
4380
  import * as module from "module";
3949
4381
  import { fileURLToPath } from "url";
3950
4382
  function getNativeBinding() {
3951
- const platform2 = os3.platform();
3952
- const arch2 = os3.arch();
4383
+ const platform2 = os4.platform();
4384
+ const arch2 = os4.arch();
3953
4385
  let bindingName;
3954
4386
  if (platform2 === "darwin" && arch2 === "arm64") {
3955
4387
  bindingName = "codebase-index-native.darwin-arm64.node";
@@ -3967,19 +4399,19 @@ function getNativeBinding() {
3967
4399
  let currentDir;
3968
4400
  let requireTarget;
3969
4401
  if (typeof import.meta !== "undefined" && import.meta.url) {
3970
- currentDir = path9.dirname(fileURLToPath(import.meta.url));
4402
+ currentDir = path10.dirname(fileURLToPath(import.meta.url));
3971
4403
  requireTarget = import.meta.url;
3972
4404
  } else if (typeof __dirname !== "undefined") {
3973
4405
  currentDir = __dirname;
3974
4406
  requireTarget = __filename;
3975
4407
  } else {
3976
4408
  currentDir = process.cwd();
3977
- requireTarget = path9.join(currentDir, "index.js");
4409
+ requireTarget = path10.join(currentDir, "index.js");
3978
4410
  }
3979
4411
  const normalizedDir = currentDir.replace(/\\/g, "/");
3980
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path9.join("src", "native"));
3981
- const packageRoot = isDevMode ? path9.resolve(currentDir, "../..") : path9.resolve(currentDir, "..");
3982
- const nativePath = path9.join(packageRoot, "native", bindingName);
4412
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path10.join("src", "native"));
4413
+ const packageRoot = isDevMode ? path10.resolve(currentDir, "../..") : path10.resolve(currentDir, "..");
4414
+ const nativePath = path10.join(packageRoot, "native", bindingName);
3983
4415
  const require2 = module.createRequire(requireTarget);
3984
4416
  return require2(nativePath);
3985
4417
  }
@@ -4021,6 +4453,12 @@ function createMockNativeBinding() {
4021
4453
  constructor() {
4022
4454
  throw error;
4023
4455
  }
4456
+ static openReadOnly() {
4457
+ throw error;
4458
+ }
4459
+ static createEmptyReadOnly() {
4460
+ throw error;
4461
+ }
4024
4462
  close() {
4025
4463
  throw error;
4026
4464
  }
@@ -4124,6 +4562,12 @@ var VectorStore = class {
4124
4562
  load() {
4125
4563
  this.inner.load();
4126
4564
  }
4565
+ loadStrict() {
4566
+ this.inner.loadStrict();
4567
+ }
4568
+ hasFingerprint() {
4569
+ return this.inner.hasFingerprint();
4570
+ }
4127
4571
  count() {
4128
4572
  return this.inner.count();
4129
4573
  }
@@ -4406,12 +4850,24 @@ var InvertedIndex = class {
4406
4850
  return this.inner.documentCount();
4407
4851
  }
4408
4852
  };
4409
- var Database = class {
4853
+ var Database = class _Database {
4410
4854
  inner;
4411
4855
  closed = false;
4412
4856
  constructor(dbPath) {
4413
4857
  this.inner = new native.Database(dbPath);
4414
4858
  }
4859
+ static fromNative(inner) {
4860
+ const database = Object.create(_Database.prototype);
4861
+ database.inner = inner;
4862
+ database.closed = false;
4863
+ return database;
4864
+ }
4865
+ static openReadOnly(dbPath) {
4866
+ return _Database.fromNative(native.Database.openReadOnly(dbPath));
4867
+ }
4868
+ static createEmptyReadOnly() {
4869
+ return _Database.fromNative(native.Database.createEmptyReadOnly());
4870
+ }
4415
4871
  throwIfClosed() {
4416
4872
  if (this.closed) {
4417
4873
  throw new Error("Database is closed");
@@ -4688,7 +5144,7 @@ var Database = class {
4688
5144
  };
4689
5145
 
4690
5146
  // src/tools/changed-files.ts
4691
- import * as path10 from "path";
5147
+ import * as path11 from "path";
4692
5148
  import { execFile } from "child_process";
4693
5149
  import { promisify } from "util";
4694
5150
  var execFileAsync = promisify(execFile);
@@ -4776,8 +5232,8 @@ function normalizeFiles(rawFiles, projectRoot) {
4776
5232
  for (const raw of rawFiles) {
4777
5233
  const trimmed = raw.trim();
4778
5234
  if (!trimmed) continue;
4779
- const absolute = path10.resolve(projectRoot, trimmed);
4780
- const relative11 = path10.relative(projectRoot, absolute);
5235
+ const absolute = path11.resolve(projectRoot, trimmed);
5236
+ const relative11 = path11.relative(projectRoot, absolute);
4781
5237
  const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
4782
5238
  if (!seen.has(cleaned)) {
4783
5239
  seen.add(cleaned);
@@ -4789,7 +5245,7 @@ function normalizeFiles(rawFiles, projectRoot) {
4789
5245
 
4790
5246
  // src/indexer/git-blame.ts
4791
5247
  import { execFile as execFile2 } from "child_process";
4792
- import * as path11 from "path";
5248
+ import * as path12 from "path";
4793
5249
  import { promisify as promisify2 } from "util";
4794
5250
  var execFileAsync2 = promisify2(execFile2);
4795
5251
  function parseGitBlamePorcelain(output) {
@@ -4827,7 +5283,7 @@ function parseGitBlamePorcelain(output) {
4827
5283
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4828
5284
  }
4829
5285
  async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
4830
- const relativePath = path11.relative(projectRoot, filePath);
5286
+ const relativePath = path12.relative(projectRoot, filePath);
4831
5287
  try {
4832
5288
  const { stdout } = await execFileAsync2(
4833
5289
  "git",
@@ -4921,6 +5377,7 @@ function isSqliteCorruptionError(error) {
4921
5377
  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");
4922
5378
  }
4923
5379
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5380
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
4924
5381
  function metadataFromBlame(blame) {
4925
5382
  if (!blame) {
4926
5383
  return {};
@@ -5118,9 +5575,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5118
5575
  return true;
5119
5576
  }
5120
5577
  function isPathWithinRoot(filePath, rootPath) {
5121
- const normalizedFilePath = path12.resolve(filePath);
5122
- const normalizedRoot = path12.resolve(rootPath);
5123
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
5578
+ const normalizedFilePath = path13.resolve(filePath);
5579
+ const normalizedRoot = path13.resolve(rootPath);
5580
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5124
5581
  }
5125
5582
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5126
5583
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -6186,26 +6643,133 @@ var Indexer = class {
6186
6643
  queryCacheTtlMs = 5 * 60 * 1e3;
6187
6644
  querySimilarityThreshold = 0.85;
6188
6645
  indexCompatibility = null;
6189
- indexingLockPath = "";
6646
+ activeIndexLease = null;
6647
+ initializationPromise = null;
6648
+ initializationMode = "none";
6649
+ readIssues = [];
6650
+ retiredDatabases = [];
6651
+ readerArtifactFingerprint = null;
6652
+ writerArtifactFingerprint = null;
6653
+ readerArtifactRetryAfter = /* @__PURE__ */ new Map();
6190
6654
  constructor(projectRoot, config, host = "opencode") {
6191
6655
  this.projectRoot = projectRoot;
6192
6656
  this.config = config;
6193
6657
  this.host = host;
6194
6658
  this.indexPath = this.getIndexPath();
6195
- this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6196
- this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6197
- this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
6659
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6660
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6198
6661
  this.logger = initializeLogger(config.debug);
6199
6662
  }
6200
6663
  getIndexPath() {
6201
6664
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6202
6665
  }
6666
+ isLocalProjectIndexPath() {
6667
+ const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6668
+ if (this.host !== "opencode") {
6669
+ localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6670
+ }
6671
+ return localProjectIndexPaths.some((localPath) => {
6672
+ if (!existsSync8(localPath) || !existsSync8(this.indexPath)) {
6673
+ return path13.resolve(this.indexPath) === path13.resolve(localPath);
6674
+ }
6675
+ const indexStats = statSync3(this.indexPath);
6676
+ const localStats = statSync3(localPath);
6677
+ return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6678
+ });
6679
+ }
6680
+ resetLoadedIndexState(retireDatabase = false) {
6681
+ if (this.database) {
6682
+ if (retireDatabase) {
6683
+ this.retiredDatabases.push(this.database);
6684
+ } else {
6685
+ this.database.close();
6686
+ }
6687
+ }
6688
+ this.store = null;
6689
+ this.invertedIndex = null;
6690
+ this.database = null;
6691
+ this.provider = null;
6692
+ this.configuredProviderInfo = null;
6693
+ this.reranker = null;
6694
+ this.indexCompatibility = null;
6695
+ this.initializationMode = "none";
6696
+ this.readIssues = [];
6697
+ this.readerArtifactFingerprint = null;
6698
+ this.writerArtifactFingerprint = null;
6699
+ this.readerArtifactRetryAfter.clear();
6700
+ this.fileHashCache.clear();
6701
+ }
6702
+ refreshLoadedIndexState() {
6703
+ if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
6704
+ this.store.load();
6705
+ this.invertedIndex.load();
6706
+ this.fileHashCache.clear();
6707
+ this.loadFileHashCache();
6708
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
6709
+ this.readIssues = [];
6710
+ this.readerArtifactRetryAfter.clear();
6711
+ }
6712
+ async withIndexMutationLease(operation, callback) {
6713
+ const lease = acquireIndexLock(this.indexPath, operation);
6714
+ this.indexPath = lease.canonicalIndexPath;
6715
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6716
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6717
+ this.activeIndexLease = lease;
6718
+ let result;
6719
+ let callbackError;
6720
+ let callbackFailed = false;
6721
+ try {
6722
+ result = await callback(lease.recoveries.map(({ owner }) => owner));
6723
+ } catch (error) {
6724
+ callbackFailed = true;
6725
+ callbackError = error;
6726
+ }
6727
+ if (!callbackFailed) {
6728
+ try {
6729
+ completeLeaseRecovery(lease);
6730
+ this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
6731
+ } catch (error) {
6732
+ callbackFailed = true;
6733
+ callbackError = error;
6734
+ }
6735
+ }
6736
+ let releaseError;
6737
+ try {
6738
+ if (!releaseIndexLock(lease)) {
6739
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
6740
+ this.writerArtifactFingerprint = null;
6741
+ if (this.activeIndexLease?.owner.token === lease.owner.token) {
6742
+ this.activeIndexLease = null;
6743
+ }
6744
+ } else if (this.activeIndexLease?.owner.token === lease.owner.token) {
6745
+ this.activeIndexLease = null;
6746
+ }
6747
+ } catch (error) {
6748
+ releaseError = error;
6749
+ this.writerArtifactFingerprint = null;
6750
+ if (!existsSync8(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6751
+ this.activeIndexLease = null;
6752
+ }
6753
+ }
6754
+ if (releaseError !== void 0) {
6755
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
6756
+ throw releaseError;
6757
+ }
6758
+ if (callbackFailed) throw callbackError;
6759
+ return result;
6760
+ }
6761
+ requireActiveLease() {
6762
+ if (!this.activeIndexLease) {
6763
+ throw new Error("Index mutation attempted without an active interprocess lease");
6764
+ }
6765
+ return this.activeIndexLease;
6766
+ }
6203
6767
  loadFileHashCache() {
6204
- if (!existsSync7(this.fileHashCachePath)) {
6768
+ if (!existsSync8(this.fileHashCachePath)) {
6205
6769
  return;
6206
6770
  }
6207
6771
  try {
6208
- const data = readFileSync6(this.fileHashCachePath, "utf-8");
6772
+ const data = readFileSync7(this.fileHashCachePath, "utf-8");
6209
6773
  const parsed = JSON.parse(data);
6210
6774
  this.fileHashCache = new Map(Object.entries(parsed));
6211
6775
  } catch (error) {
@@ -6225,15 +6789,26 @@ var Indexer = class {
6225
6789
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6226
6790
  }
6227
6791
  atomicWriteSync(targetPath, data) {
6228
- const tempPath = `${targetPath}.tmp`;
6229
- mkdirSync2(path12.dirname(targetPath), { recursive: true });
6230
- writeFileSync2(tempPath, data);
6231
- renameSync(tempPath, targetPath);
6792
+ const lease = this.requireActiveLease();
6793
+ const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6794
+ mkdirSync3(path13.dirname(targetPath), { recursive: true });
6795
+ try {
6796
+ writeFileSync3(tempPath, data);
6797
+ renameSync2(tempPath, targetPath);
6798
+ } finally {
6799
+ removeLeaseTemporaryPath(tempPath);
6800
+ }
6801
+ }
6802
+ saveInvertedIndex(invertedIndex) {
6803
+ this.atomicWriteSync(
6804
+ path13.join(this.indexPath, "inverted-index.json"),
6805
+ invertedIndex.serialize()
6806
+ );
6232
6807
  }
6233
6808
  getScopedRoots() {
6234
- const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
6809
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6235
6810
  for (const kbRoot of this.config.knowledgeBases) {
6236
- roots.add(path12.resolve(this.projectRoot, kbRoot));
6811
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
6237
6812
  }
6238
6813
  return Array.from(roots);
6239
6814
  }
@@ -6242,29 +6817,29 @@ var Indexer = class {
6242
6817
  if (this.config.scope !== "global") {
6243
6818
  return branchName;
6244
6819
  }
6245
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6820
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6246
6821
  return `${projectHash}:${branchName}`;
6247
6822
  }
6248
6823
  getBranchCatalogKeyFor(branchName) {
6249
6824
  if (this.config.scope !== "global") {
6250
6825
  return branchName;
6251
6826
  }
6252
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6827
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6253
6828
  return `${projectHash}:${branchName}`;
6254
6829
  }
6255
6830
  getLegacyBranchCatalogKey() {
6256
6831
  return this.currentBranch || "default";
6257
6832
  }
6258
6833
  getLegacyMigrationMetadataKey() {
6259
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6834
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6260
6835
  return `index.globalBranchMigration.${projectHash}`;
6261
6836
  }
6262
6837
  getProjectEmbeddingStrategyMetadataKey() {
6263
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6838
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6264
6839
  return `index.embeddingStrategyVersion.${projectHash}`;
6265
6840
  }
6266
6841
  getProjectForceReembedMetadataKey() {
6267
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6842
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6268
6843
  return `index.forceReembed.${projectHash}`;
6269
6844
  }
6270
6845
  hasProjectForceReembedPending() {
@@ -6358,7 +6933,7 @@ var Indexer = class {
6358
6933
  if (!this.database) {
6359
6934
  return { chunkIds, symbolIds };
6360
6935
  }
6361
- const projectRootPath = path12.resolve(this.projectRoot);
6936
+ const projectRootPath = path13.resolve(this.projectRoot);
6362
6937
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6363
6938
  ...Array.from(this.fileHashCache.keys()).filter(
6364
6939
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6381,7 +6956,7 @@ var Indexer = class {
6381
6956
  if (this.config.scope !== "global") {
6382
6957
  return this.getBranchCatalogCleanupKeys();
6383
6958
  }
6384
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6959
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6385
6960
  const keys = /* @__PURE__ */ new Set();
6386
6961
  const projectChunkIdSet = new Set(projectChunkIds);
6387
6962
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6465,7 +7040,7 @@ var Indexer = class {
6465
7040
  if (!this.database || this.config.scope !== "global") {
6466
7041
  return false;
6467
7042
  }
6468
- const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7043
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6469
7044
  const roots = this.getScopedRoots();
6470
7045
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6471
7046
  return this.database.getAllBranches().some(
@@ -6499,7 +7074,7 @@ var Indexer = class {
6499
7074
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6500
7075
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6501
7076
  ]);
6502
- const projectRootPath = path12.resolve(this.projectRoot);
7077
+ const projectRootPath = path13.resolve(this.projectRoot);
6503
7078
  const projectLocalFilePaths = new Set(
6504
7079
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6505
7080
  );
@@ -6561,34 +7136,27 @@ var Indexer = class {
6561
7136
  database.gcOrphanEmbeddings();
6562
7137
  database.gcOrphanChunks();
6563
7138
  store.save();
6564
- invertedIndex.save();
7139
+ this.saveInvertedIndex(invertedIndex);
6565
7140
  return {
6566
7141
  removedChunkIds: removedChunkIdList,
6567
7142
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6568
7143
  };
6569
7144
  }
6570
- checkForInterruptedIndexing() {
6571
- return existsSync7(this.indexingLockPath);
6572
- }
6573
- acquireIndexingLock() {
6574
- const lockData = {
6575
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6576
- pid: process.pid
6577
- };
6578
- writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
6579
- }
6580
- releaseIndexingLock() {
6581
- if (existsSync7(this.indexingLockPath)) {
6582
- unlinkSync(this.indexingLockPath);
7145
+ async recoverFromInterruptedIndexingUnlocked(owners) {
7146
+ for (const owner of owners) {
7147
+ this.logger.warn("Detected interrupted indexing session, recovering...", {
7148
+ pid: owner.pid,
7149
+ hostname: owner.hostname,
7150
+ operation: owner.operation,
7151
+ startedAt: owner.startedAt
7152
+ });
6583
7153
  }
6584
- }
6585
- async recoverFromInterruptedIndexing() {
6586
- this.logger.warn("Detected interrupted indexing session, recovering...");
6587
- if (existsSync7(this.fileHashCachePath)) {
6588
- unlinkSync(this.fileHashCachePath);
7154
+ if (this.config.scope === "global") {
7155
+ if (existsSync8(this.fileHashCachePath)) {
7156
+ unlinkSync(this.fileHashCachePath);
7157
+ }
7158
+ await this.healthCheckUnlocked();
6589
7159
  }
6590
- await this.healthCheck();
6591
- this.releaseIndexingLock();
6592
7160
  this.logger.info("Recovery complete, next index will re-process all files");
6593
7161
  }
6594
7162
  loadFailedBatches(maxChunkTokens) {
@@ -6604,10 +7172,10 @@ var Indexer = class {
6604
7172
  }
6605
7173
  }
6606
7174
  loadSerializedFailedBatches() {
6607
- if (!existsSync7(this.failedBatchesPath)) {
7175
+ if (!existsSync8(this.failedBatchesPath)) {
6608
7176
  return [];
6609
7177
  }
6610
- const data = readFileSync6(this.failedBatchesPath, "utf-8");
7178
+ const data = readFileSync7(this.failedBatchesPath, "utf-8");
6611
7179
  const parsed = JSON.parse(data);
6612
7180
  return parsed.map((batch) => {
6613
7181
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6624,7 +7192,7 @@ var Indexer = class {
6624
7192
  }
6625
7193
  saveFailedBatches(batches) {
6626
7194
  if (batches.length === 0) {
6627
- if (existsSync7(this.failedBatchesPath)) {
7195
+ if (existsSync8(this.failedBatchesPath)) {
6628
7196
  try {
6629
7197
  unlinkSync(this.failedBatchesPath);
6630
7198
  } catch {
@@ -6632,7 +7200,7 @@ var Indexer = class {
6632
7200
  }
6633
7201
  return;
6634
7202
  }
6635
- writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7203
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6636
7204
  }
6637
7205
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6638
7206
  const retryableById = /* @__PURE__ */ new Map();
@@ -6826,6 +7394,222 @@ var Indexer = class {
6826
7394
  return parts.join("\n");
6827
7395
  }
6828
7396
  async initialize() {
7397
+ if (this.initializationPromise) {
7398
+ await this.initializationPromise;
7399
+ }
7400
+ if (this.isInitializedFor("reader")) {
7401
+ return;
7402
+ }
7403
+ await this.initializeOnce("reader", [], { skipAutoGc: true });
7404
+ }
7405
+ async initializeOnce(mode, recoveredOwners, options) {
7406
+ if (this.initializationPromise) {
7407
+ await this.initializationPromise;
7408
+ if (this.isInitializedFor(mode)) {
7409
+ return;
7410
+ }
7411
+ return this.initializeOnce(mode, recoveredOwners, options);
7412
+ }
7413
+ if (this.isInitializedFor(mode)) {
7414
+ return;
7415
+ }
7416
+ const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
7417
+ this.resetLoadedIndexState();
7418
+ throw error;
7419
+ }).finally(() => {
7420
+ if (this.initializationPromise === initialization) {
7421
+ this.initializationPromise = null;
7422
+ }
7423
+ });
7424
+ this.initializationPromise = initialization;
7425
+ await initialization;
7426
+ }
7427
+ isInitializedFor(mode) {
7428
+ const hasState = Boolean(
7429
+ this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
7430
+ );
7431
+ if (!hasState) {
7432
+ return false;
7433
+ }
7434
+ return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
7435
+ }
7436
+ recordReadIssue(component, message, error) {
7437
+ this.readIssues.push(this.createReadIssue(component, message));
7438
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7439
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7440
+ }
7441
+ createReadIssue(component, message) {
7442
+ return {
7443
+ component,
7444
+ message,
7445
+ blocking: component !== "keyword"
7446
+ };
7447
+ }
7448
+ getVectorReadIssueMessage() {
7449
+ if (this.config.scope === "global") {
7450
+ return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
7451
+ }
7452
+ if (!this.isLocalProjectIndexPath()) {
7453
+ 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.";
7454
+ }
7455
+ 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.";
7456
+ }
7457
+ getKeywordReadIssueMessage() {
7458
+ if (this.config.scope === "global") {
7459
+ 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.";
7460
+ }
7461
+ if (!this.isLocalProjectIndexPath()) {
7462
+ 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.";
7463
+ }
7464
+ 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.";
7465
+ }
7466
+ getDatabaseReadIssueMessage() {
7467
+ if (this.config.scope === "global") {
7468
+ return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
7469
+ }
7470
+ if (!this.isLocalProjectIndexPath()) {
7471
+ 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.";
7472
+ }
7473
+ return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
7474
+ }
7475
+ getReaderFileFingerprint(filePath, identityOnly = false) {
7476
+ try {
7477
+ const stats = statSync3(filePath);
7478
+ if (identityOnly) {
7479
+ return `${stats.dev}:${stats.ino}`;
7480
+ }
7481
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
7482
+ } catch (error) {
7483
+ return `unavailable:${getErrorMessage2(error)}`;
7484
+ }
7485
+ }
7486
+ captureReaderArtifactFingerprint() {
7487
+ const storePath = path13.join(this.indexPath, "vectors");
7488
+ return {
7489
+ vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7490
+ keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7491
+ database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7492
+ databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7493
+ };
7494
+ }
7495
+ refreshReaderArtifacts() {
7496
+ if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
7497
+ return;
7498
+ }
7499
+ const previousFingerprint = this.readerArtifactFingerprint;
7500
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7501
+ const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
7502
+ const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
7503
+ const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
7504
+ const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
7505
+ const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
7506
+ const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7507
+ if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
7508
+ return;
7509
+ }
7510
+ const setIssue = (component, message, error) => {
7511
+ if (!issues.has(component)) {
7512
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7513
+ }
7514
+ issues.set(component, this.createReadIssue(component, message));
7515
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7516
+ };
7517
+ const storePath = path13.join(this.indexPath, "vectors");
7518
+ const vectorMetadataPath = `${storePath}.meta.json`;
7519
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7520
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7521
+ if (vectorsChanged || retryDue("vectors")) {
7522
+ const vectorStoreExists = existsSync8(storePath);
7523
+ const vectorMetadataExists = existsSync8(vectorMetadataPath);
7524
+ if (vectorStoreExists && vectorMetadataExists) {
7525
+ try {
7526
+ const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
7527
+ store.loadStrict();
7528
+ this.store = store;
7529
+ issues.delete("vectors");
7530
+ this.readerArtifactRetryAfter.delete("vectors");
7531
+ } catch (error) {
7532
+ setIssue("vectors", this.getVectorReadIssueMessage(), error);
7533
+ }
7534
+ } else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
7535
+ setIssue("vectors", this.getVectorReadIssueMessage());
7536
+ }
7537
+ }
7538
+ if (keywordChanged || retryDue("keyword") || !existsSync8(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7539
+ if (existsSync8(invertedIndexPath)) {
7540
+ try {
7541
+ const invertedIndex = new InvertedIndex(invertedIndexPath);
7542
+ invertedIndex.load();
7543
+ this.invertedIndex = invertedIndex;
7544
+ issues.delete("keyword");
7545
+ this.readerArtifactRetryAfter.delete("keyword");
7546
+ } catch (error) {
7547
+ setIssue("keyword", this.getKeywordReadIssueMessage(), error);
7548
+ }
7549
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
7550
+ setIssue("keyword", this.getKeywordReadIssueMessage());
7551
+ }
7552
+ }
7553
+ if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7554
+ if (existsSync8(dbPath)) {
7555
+ try {
7556
+ const database = Database.openReadOnly(dbPath);
7557
+ if (this.database) {
7558
+ this.retiredDatabases.push(this.database);
7559
+ }
7560
+ this.database = database;
7561
+ issues.delete("database");
7562
+ this.readerArtifactRetryAfter.delete("database");
7563
+ } catch (error) {
7564
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7565
+ }
7566
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
7567
+ setIssue("database", this.getDatabaseReadIssueMessage());
7568
+ }
7569
+ }
7570
+ if (!issues.has("database")) {
7571
+ try {
7572
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7573
+ } catch (error) {
7574
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7575
+ }
7576
+ }
7577
+ this.readIssues = Array.from(issues.values());
7578
+ this.readerArtifactFingerprint = currentFingerprint;
7579
+ }
7580
+ refreshInactiveWriterArtifacts() {
7581
+ if (this.initializationMode !== "writer" || this.activeIndexLease) {
7582
+ return true;
7583
+ }
7584
+ const previousFingerprint = this.writerArtifactFingerprint;
7585
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7586
+ const retryDue = this.readIssues.some(
7587
+ (issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
7588
+ );
7589
+ const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7590
+ if (!artifactsChanged && !retryDue) {
7591
+ return true;
7592
+ }
7593
+ if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
7594
+ return false;
7595
+ }
7596
+ this.initializationMode = "reader";
7597
+ this.readerArtifactFingerprint = previousFingerprint;
7598
+ try {
7599
+ this.refreshReaderArtifacts();
7600
+ this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
7601
+ } finally {
7602
+ this.readerArtifactFingerprint = null;
7603
+ this.initializationMode = "writer";
7604
+ }
7605
+ return true;
7606
+ }
7607
+ async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
7608
+ if (mode === "writer") {
7609
+ this.requireActiveLease();
7610
+ }
7611
+ this.readIssues = [];
7612
+ this.readerArtifactRetryAfter.clear();
6829
7613
  if (this.config.embeddingProvider === "custom") {
6830
7614
  if (!this.config.customProvider) {
6831
7615
  throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
@@ -6857,36 +7641,103 @@ var Indexer = class {
6857
7641
  });
6858
7642
  }
6859
7643
  }
6860
- await fsPromises2.mkdir(this.indexPath, { recursive: true });
6861
7644
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6862
- const storePath = path12.join(this.indexPath, "vectors");
6863
- this.store = new VectorStore(storePath, dimensions);
6864
- const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
6865
- if (existsSync7(indexFilePath)) {
6866
- this.store.load();
6867
- }
6868
- const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
6869
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6870
- try {
6871
- this.invertedIndex.load();
6872
- } catch {
6873
- if (existsSync7(invertedIndexPath)) {
6874
- await fsPromises2.unlink(invertedIndexPath);
7645
+ const storePath = path13.join(this.indexPath, "vectors");
7646
+ const vectorMetadataPath = `${storePath}.meta.json`;
7647
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7648
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7649
+ let dbIsNew = !existsSync8(dbPath);
7650
+ const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7651
+ if (mode === "writer") {
7652
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
7653
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7654
+ throw new Error(
7655
+ "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
7656
+ );
7657
+ }
7658
+ for (const recoveredOwner of recoveredOwners) {
7659
+ recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
7660
+ storePath,
7661
+ `${storePath}.meta.json`
7662
+ ]);
7663
+ }
7664
+ if (recoveredOwners.length > 0 && this.config.scope === "project") {
7665
+ await this.resetLocalIndexArtifacts();
7666
+ }
7667
+ this.store = new VectorStore(storePath, dimensions);
7668
+ if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
7669
+ this.store.load();
6875
7670
  }
6876
7671
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6877
- }
6878
- const dbPath = path12.join(this.indexPath, "codebase.db");
6879
- let dbIsNew = !existsSync7(dbPath);
6880
- try {
6881
- this.database = new Database(dbPath);
6882
- } catch (error) {
6883
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6884
- throw error;
7672
+ try {
7673
+ this.invertedIndex.load();
7674
+ } catch {
7675
+ if (existsSync8(invertedIndexPath)) {
7676
+ await fsPromises2.unlink(invertedIndexPath);
7677
+ }
7678
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7679
+ }
7680
+ try {
7681
+ this.database = new Database(dbPath);
7682
+ } catch (error) {
7683
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7684
+ throw error;
7685
+ }
7686
+ this.store = new VectorStore(storePath, dimensions);
7687
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7688
+ this.database = new Database(dbPath);
7689
+ dbIsNew = true;
6885
7690
  }
7691
+ } else {
6886
7692
  this.store = new VectorStore(storePath, dimensions);
7693
+ const vectorStoreExists = existsSync8(storePath);
7694
+ const vectorMetadataExists = existsSync8(vectorMetadataPath);
7695
+ const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7696
+ if (vectorStoreExists !== vectorMetadataExists) {
7697
+ this.recordReadIssue("vectors", vectorReadFailureMessage);
7698
+ } else if (vectorStoreExists) {
7699
+ try {
7700
+ this.store.loadStrict();
7701
+ } catch (error) {
7702
+ this.recordReadIssue("vectors", vectorReadFailureMessage, error);
7703
+ this.store = new VectorStore(storePath, dimensions);
7704
+ }
7705
+ }
6887
7706
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6888
- this.database = new Database(dbPath);
6889
- dbIsNew = true;
7707
+ if (existsSync8(invertedIndexPath)) {
7708
+ try {
7709
+ this.invertedIndex.load();
7710
+ } catch (error) {
7711
+ this.recordReadIssue(
7712
+ "keyword",
7713
+ this.getKeywordReadIssueMessage(),
7714
+ error
7715
+ );
7716
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7717
+ }
7718
+ } else if (this.store.count() > 0) {
7719
+ this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7720
+ }
7721
+ if (existsSync8(dbPath)) {
7722
+ try {
7723
+ this.database = Database.openReadOnly(dbPath);
7724
+ } catch (error) {
7725
+ this.recordReadIssue(
7726
+ "database",
7727
+ this.getDatabaseReadIssueMessage(),
7728
+ error
7729
+ );
7730
+ this.database = Database.createEmptyReadOnly();
7731
+ }
7732
+ } else {
7733
+ this.database = Database.createEmptyReadOnly();
7734
+ if (this.store.count() > 0) {
7735
+ this.recordReadIssue(
7736
+ "database",
7737
+ `Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
7738
+ );
7739
+ }
7740
+ }
6890
7741
  }
6891
7742
  if (isGitRepo(this.projectRoot)) {
6892
7743
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6900,10 +7751,10 @@ var Indexer = class {
6900
7751
  this.baseBranch = "default";
6901
7752
  this.logger.branch("debug", "Not a git repository, using default branch");
6902
7753
  }
6903
- if (this.checkForInterruptedIndexing()) {
6904
- await this.recoverFromInterruptedIndexing();
7754
+ if (mode === "writer" && recoveredOwners.length > 0) {
7755
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6905
7756
  }
6906
- if (dbIsNew && this.store.count() > 0) {
7757
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6907
7758
  this.migrateFromLegacyIndex();
6908
7759
  }
6909
7760
  this.loadFileHashCache();
@@ -6915,9 +7766,11 @@ var Indexer = class {
6915
7766
  configuredProviderInfo: this.configuredProviderInfo
6916
7767
  });
6917
7768
  }
6918
- if (this.config.indexing.autoGc) {
7769
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6919
7770
  await this.maybeRunAutoGc();
6920
7771
  }
7772
+ this.initializationMode = mode;
7773
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6921
7774
  }
6922
7775
  async maybeRunAutoGc() {
6923
7776
  if (!this.database) return;
@@ -6934,7 +7787,7 @@ var Indexer = class {
6934
7787
  }
6935
7788
  }
6936
7789
  if (shouldRunGc) {
6937
- const result = await this.healthCheck();
7790
+ const result = await this.healthCheckUnlocked();
6938
7791
  if (result.warning) {
6939
7792
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
6940
7793
  } else {
@@ -6956,7 +7809,7 @@ var Indexer = class {
6956
7809
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6957
7810
  return {
6958
7811
  resetCorruptedIndex: true,
6959
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
7812
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
6960
7813
  };
6961
7814
  }
6962
7815
  throw error;
@@ -6971,28 +7824,29 @@ var Indexer = class {
6971
7824
  return;
6972
7825
  }
6973
7826
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6974
- const storeBasePath = path12.join(this.indexPath, "vectors");
6975
- const storeIndexPath = `${storeBasePath}.usearch`;
7827
+ const storeBasePath = path13.join(this.indexPath, "vectors");
7828
+ const storeIndexPath = storeBasePath;
6976
7829
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6977
- const backupIndexPath = `${storeIndexPath}.bak`;
6978
- const backupMetadataPath = `${storeMetadataPath}.bak`;
7830
+ const lease = this.requireActiveLease();
7831
+ const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
7832
+ const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
6979
7833
  let backedUpIndex = false;
6980
7834
  let backedUpMetadata = false;
6981
7835
  let rebuiltCount = 0;
6982
7836
  let skippedCount = 0;
6983
- if (existsSync7(backupIndexPath)) {
7837
+ if (existsSync8(backupIndexPath)) {
6984
7838
  unlinkSync(backupIndexPath);
6985
7839
  }
6986
- if (existsSync7(backupMetadataPath)) {
7840
+ if (existsSync8(backupMetadataPath)) {
6987
7841
  unlinkSync(backupMetadataPath);
6988
7842
  }
6989
7843
  try {
6990
- if (existsSync7(storeIndexPath)) {
6991
- renameSync(storeIndexPath, backupIndexPath);
7844
+ if (existsSync8(storeIndexPath)) {
7845
+ renameSync2(storeIndexPath, backupIndexPath);
6992
7846
  backedUpIndex = true;
6993
7847
  }
6994
- if (existsSync7(storeMetadataPath)) {
6995
- renameSync(storeMetadataPath, backupMetadataPath);
7848
+ if (existsSync8(storeMetadataPath)) {
7849
+ renameSync2(storeMetadataPath, backupMetadataPath);
6996
7850
  backedUpMetadata = true;
6997
7851
  }
6998
7852
  store.clear();
@@ -7012,10 +7866,10 @@ var Indexer = class {
7012
7866
  rebuiltCount += 1;
7013
7867
  }
7014
7868
  store.save();
7015
- if (backedUpIndex && existsSync7(backupIndexPath)) {
7869
+ if (backedUpIndex && existsSync8(backupIndexPath)) {
7016
7870
  unlinkSync(backupIndexPath);
7017
7871
  }
7018
- if (backedUpMetadata && existsSync7(backupMetadataPath)) {
7872
+ if (backedUpMetadata && existsSync8(backupMetadataPath)) {
7019
7873
  unlinkSync(backupMetadataPath);
7020
7874
  }
7021
7875
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -7028,17 +7882,17 @@ var Indexer = class {
7028
7882
  store.clear();
7029
7883
  } catch {
7030
7884
  }
7031
- if (existsSync7(storeIndexPath)) {
7885
+ if (existsSync8(storeIndexPath)) {
7032
7886
  unlinkSync(storeIndexPath);
7033
7887
  }
7034
- if (existsSync7(storeMetadataPath)) {
7888
+ if (existsSync8(storeMetadataPath)) {
7035
7889
  unlinkSync(storeMetadataPath);
7036
7890
  }
7037
- if (backedUpIndex && existsSync7(backupIndexPath)) {
7038
- renameSync(backupIndexPath, storeIndexPath);
7891
+ if (backedUpIndex && existsSync8(backupIndexPath)) {
7892
+ renameSync2(backupIndexPath, storeIndexPath);
7039
7893
  }
7040
- if (backedUpMetadata && existsSync7(backupMetadataPath)) {
7041
- renameSync(backupMetadataPath, storeMetadataPath);
7894
+ if (backedUpMetadata && existsSync8(backupMetadataPath)) {
7895
+ renameSync2(backupMetadataPath, storeMetadataPath);
7042
7896
  }
7043
7897
  if (backedUpIndex || backedUpMetadata) {
7044
7898
  store.load();
@@ -7052,11 +7906,37 @@ var Indexer = class {
7052
7906
  }
7053
7907
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
7054
7908
  }
7909
+ async resetLocalIndexArtifacts() {
7910
+ this.store = null;
7911
+ this.invertedIndex = null;
7912
+ this.database?.close();
7913
+ this.database = null;
7914
+ this.indexCompatibility = null;
7915
+ this.initializationMode = "none";
7916
+ this.readIssues = [];
7917
+ this.readerArtifactFingerprint = null;
7918
+ this.writerArtifactFingerprint = null;
7919
+ this.readerArtifactRetryAfter.clear();
7920
+ this.fileHashCache.clear();
7921
+ const resetPaths = [
7922
+ path13.join(this.indexPath, "codebase.db"),
7923
+ path13.join(this.indexPath, "codebase.db-shm"),
7924
+ path13.join(this.indexPath, "codebase.db-wal"),
7925
+ path13.join(this.indexPath, "vectors"),
7926
+ path13.join(this.indexPath, "vectors.usearch"),
7927
+ path13.join(this.indexPath, "vectors.meta.json"),
7928
+ path13.join(this.indexPath, "inverted-index.json"),
7929
+ path13.join(this.indexPath, "file-hashes.json"),
7930
+ path13.join(this.indexPath, "failed-batches.json")
7931
+ ];
7932
+ await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
7933
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
7934
+ }
7055
7935
  async tryResetCorruptedIndex(stage, error) {
7056
7936
  if (!isSqliteCorruptionError(error)) {
7057
7937
  return false;
7058
7938
  }
7059
- const dbPath = path12.join(this.indexPath, "codebase.db");
7939
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7060
7940
  const warning = this.getCorruptedIndexWarning(dbPath);
7061
7941
  const errorMessage = getErrorMessage2(error);
7062
7942
  if (this.config.scope === "global") {
@@ -7072,30 +7952,7 @@ var Indexer = class {
7072
7952
  dbPath,
7073
7953
  error: errorMessage
7074
7954
  });
7075
- this.store = null;
7076
- this.invertedIndex = null;
7077
- this.database?.close();
7078
- this.database = null;
7079
- this.indexCompatibility = null;
7080
- this.fileHashCache.clear();
7081
- const resetPaths = [
7082
- path12.join(this.indexPath, "codebase.db"),
7083
- path12.join(this.indexPath, "codebase.db-shm"),
7084
- path12.join(this.indexPath, "codebase.db-wal"),
7085
- path12.join(this.indexPath, "vectors.usearch"),
7086
- path12.join(this.indexPath, "inverted-index.json"),
7087
- path12.join(this.indexPath, "file-hashes.json"),
7088
- path12.join(this.indexPath, "failed-batches.json"),
7089
- path12.join(this.indexPath, "indexing.lock"),
7090
- path12.join(this.indexPath, "vectors")
7091
- ];
7092
- await Promise.all(resetPaths.map(async (targetPath) => {
7093
- try {
7094
- await fsPromises2.rm(targetPath, { recursive: true, force: true });
7095
- } catch {
7096
- }
7097
- }));
7098
- await fsPromises2.mkdir(this.indexPath, { recursive: true });
7955
+ await this.resetLocalIndexArtifacts();
7099
7956
  return true;
7100
7957
  }
7101
7958
  migrateFromLegacyIndex() {
@@ -7214,8 +8071,62 @@ var Indexer = class {
7214
8071
  return this.indexCompatibility;
7215
8072
  }
7216
8073
  async ensureInitialized() {
8074
+ let initializedReader = false;
8075
+ while (true) {
8076
+ if (this.initializationPromise) {
8077
+ await this.initializationPromise;
8078
+ }
8079
+ if (!this.isInitializedFor("reader")) {
8080
+ await this.initialize();
8081
+ initializedReader = true;
8082
+ continue;
8083
+ }
8084
+ if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
8085
+ if (!this.refreshInactiveWriterArtifacts()) {
8086
+ this.resetLoadedIndexState(true);
8087
+ await this.initialize();
8088
+ initializedReader = true;
8089
+ continue;
8090
+ }
8091
+ }
8092
+ if (this.initializationMode === "reader" && !initializedReader) {
8093
+ this.refreshReaderArtifacts();
8094
+ }
8095
+ const state = this.requireLoadedIndexState();
8096
+ return {
8097
+ ...state,
8098
+ readIssues: [...this.readIssues],
8099
+ compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
8100
+ };
8101
+ }
8102
+ }
8103
+ async ensureInitializedUnlocked(recoveredOwners = []) {
8104
+ this.requireActiveLease();
8105
+ if (this.initializationPromise) {
8106
+ await this.initializationPromise;
8107
+ }
8108
+ if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
8109
+ const retireReaderDatabase = this.initializationMode === "reader";
8110
+ this.resetLoadedIndexState(retireReaderDatabase);
8111
+ await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
8112
+ } else {
8113
+ this.refreshLoadedIndexState();
8114
+ }
8115
+ if (this.config.indexing.autoGc) {
8116
+ await this.maybeRunAutoGc();
8117
+ }
8118
+ return this.requireLoadedIndexState();
8119
+ }
8120
+ requireReadableComponents(readIssues, ...components) {
8121
+ const componentSet = new Set(components);
8122
+ const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
8123
+ if (issues.length > 0) {
8124
+ throw new Error(issues.map((issue) => issue.message).join(" "));
8125
+ }
8126
+ }
8127
+ requireLoadedIndexState() {
7217
8128
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7218
- await this.initialize();
8129
+ throw new Error("Index state is not initialized");
7219
8130
  }
7220
8131
  return {
7221
8132
  store: this.store,
@@ -7239,7 +8150,12 @@ var Indexer = class {
7239
8150
  return createCostEstimate(files, configuredProviderInfo);
7240
8151
  }
7241
8152
  async index(onProgress) {
7242
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8153
+ return this.withIndexMutationLease("index", async (recoveredOwners) => {
8154
+ return this.indexUnlocked(onProgress, recoveredOwners);
8155
+ });
8156
+ }
8157
+ async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
8158
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
7243
8159
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7244
8160
  const branchCatalogKey = this.getBranchCatalogKey();
7245
8161
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7249,7 +8165,6 @@ var Indexer = class {
7249
8165
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7250
8166
  );
7251
8167
  }
7252
- this.acquireIndexingLock();
7253
8168
  this.logger.recordIndexingStart();
7254
8169
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7255
8170
  const startTime = Date.now();
@@ -7398,7 +8313,7 @@ var Indexer = class {
7398
8313
  for (const parsed of parsedFiles) {
7399
8314
  currentFilePaths.add(parsed.path);
7400
8315
  if (parsed.chunks.length === 0) {
7401
- const relativePath = path12.relative(this.projectRoot, parsed.path);
8316
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
7402
8317
  stats.parseFailures.push(relativePath);
7403
8318
  }
7404
8319
  let fileChunkCount = 0;
@@ -7598,7 +8513,9 @@ var Indexer = class {
7598
8513
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7599
8514
  database.clearBranchSymbols(branchCatalogKey);
7600
8515
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7601
- if (backfilledBlameMetadata) {
8516
+ const vectorPath = path13.join(this.indexPath, "vectors");
8517
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync8(vectorPath) && existsSync8(`${vectorPath}.meta.json`);
8518
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
7602
8519
  store.save();
7603
8520
  }
7604
8521
  if (scopedRoots) {
@@ -7619,7 +8536,6 @@ var Indexer = class {
7619
8536
  chunksProcessed: 0,
7620
8537
  totalChunks: 0
7621
8538
  });
7622
- this.releaseIndexingLock();
7623
8539
  return stats;
7624
8540
  }
7625
8541
  if (pendingChunks.length === 0) {
@@ -7628,7 +8544,7 @@ var Indexer = class {
7628
8544
  database.clearBranchSymbols(branchCatalogKey);
7629
8545
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7630
8546
  store.save();
7631
- invertedIndex.save();
8547
+ this.saveInvertedIndex(invertedIndex);
7632
8548
  if (scopedRoots) {
7633
8549
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7634
8550
  this.clearScopedFailedBatches(scopedRoots);
@@ -7647,7 +8563,6 @@ var Indexer = class {
7647
8563
  chunksProcessed: 0,
7648
8564
  totalChunks: 0
7649
8565
  });
7650
- this.releaseIndexingLock();
7651
8566
  return stats;
7652
8567
  }
7653
8568
  onProgress?.({
@@ -7878,7 +8793,7 @@ var Indexer = class {
7878
8793
  database.clearBranchSymbols(branchCatalogKey);
7879
8794
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7880
8795
  store.save();
7881
- invertedIndex.save();
8796
+ this.saveInvertedIndex(invertedIndex);
7882
8797
  if (scopedRoots) {
7883
8798
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7884
8799
  } else {
@@ -7930,7 +8845,6 @@ var Indexer = class {
7930
8845
  chunksProcessed: stats.indexedChunks,
7931
8846
  totalChunks: pendingChunks.length
7932
8847
  });
7933
- this.releaseIndexingLock();
7934
8848
  return stats;
7935
8849
  }
7936
8850
  async getQueryEmbedding(query, provider) {
@@ -7996,8 +8910,8 @@ var Indexer = class {
7996
8910
  return intersection / union;
7997
8911
  }
7998
8912
  async search(query, limit, options) {
7999
- const { store, provider, database } = await this.ensureInitialized();
8000
- const compatibility = this.checkCompatibility();
8913
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8914
+ this.requireReadableComponents(readIssues, "vectors", "database");
8001
8915
  if (!compatibility.compatible) {
8002
8916
  throw new Error(
8003
8917
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
@@ -8011,6 +8925,7 @@ var Indexer = class {
8011
8925
  const maxResults = limit ?? this.config.search.maxResults;
8012
8926
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
8013
8927
  const fusionStrategy = this.config.search.fusionStrategy;
8928
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
8014
8929
  const rrfK = this.config.search.rrfK;
8015
8930
  const rerankTopN = this.config.search.rerankTopN;
8016
8931
  const filterByBranch = options?.filterByBranch ?? true;
@@ -8019,7 +8934,7 @@ var Indexer = class {
8019
8934
  this.logger.search("debug", "Starting search", {
8020
8935
  query,
8021
8936
  maxResults,
8022
- hybridWeight,
8937
+ hybridWeight: effectiveHybridWeight,
8023
8938
  fusionStrategy,
8024
8939
  rrfK,
8025
8940
  rerankTopN,
@@ -8027,13 +8942,22 @@ var Indexer = class {
8027
8942
  });
8028
8943
  const embeddingStartTime = performance2.now();
8029
8944
  const embeddingQuery = stripFilePathHint(query);
8030
- const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
8945
+ let embedding;
8946
+ try {
8947
+ embedding = await this.getQueryEmbedding(embeddingQuery, provider);
8948
+ } catch (error) {
8949
+ this.logger.warn("Query embedding failed; falling back to keyword-only search", {
8950
+ query,
8951
+ error: getErrorMessage2(error),
8952
+ action: "Check the embedding provider configuration and retry search after restoring provider health."
8953
+ });
8954
+ }
8031
8955
  const embeddingMs = performance2.now() - embeddingStartTime;
8032
8956
  const vectorStartTime = performance2.now();
8033
- const semanticResults = store.search(embedding, maxResults * 4);
8957
+ const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
8034
8958
  const vectorMs = performance2.now() - vectorStartTime;
8035
8959
  const keywordStartTime = performance2.now();
8036
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8960
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
8037
8961
  const keywordMs = performance2.now() - keywordStartTime;
8038
8962
  let branchChunkIds = null;
8039
8963
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -8065,12 +8989,13 @@ var Indexer = class {
8065
8989
  });
8066
8990
  }
8067
8991
  const fusionStartTime = performance2.now();
8992
+ const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
8068
8993
  const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
8069
8994
  fusionStrategy,
8070
8995
  rrfK,
8071
8996
  rerankTopN,
8072
8997
  limit: maxResults,
8073
- hybridWeight,
8998
+ hybridWeight: rankingHybridWeight,
8074
8999
  prioritizeSourcePaths: sourceIntent
8075
9000
  });
8076
9001
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -8170,8 +9095,7 @@ var Indexer = class {
8170
9095
  })
8171
9096
  );
8172
9097
  }
8173
- async keywordSearch(query, limit) {
8174
- const { store, invertedIndex } = await this.ensureInitialized();
9098
+ async keywordSearch(query, limit, store, invertedIndex) {
8175
9099
  const scores = invertedIndex.search(query);
8176
9100
  if (scores.size === 0) {
8177
9101
  return [];
@@ -8189,24 +9113,54 @@ var Indexer = class {
8189
9113
  return results.slice(0, limit);
8190
9114
  }
8191
9115
  async getStatus() {
8192
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9116
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
8193
9117
  const failedBatchesCount = this.getFailedBatchesCount();
9118
+ const vectorCount = store.count();
9119
+ const statusReadIssues = [...readIssues];
9120
+ let startupWarning = "";
9121
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9122
+ try {
9123
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9124
+ } catch (error) {
9125
+ const message = this.getDatabaseReadIssueMessage();
9126
+ statusReadIssues.push(this.createReadIssue("database", message));
9127
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9128
+ this.recordReadIssue("database", message, error);
9129
+ }
9130
+ }
9131
+ }
9132
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9133
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9134
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
8194
9135
  return {
8195
- indexed: store.count() > 0,
8196
- vectorCount: store.count(),
9136
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9137
+ vectorCount,
8197
9138
  provider: configuredProviderInfo.provider,
8198
9139
  model: configuredProviderInfo.modelInfo.model,
8199
9140
  indexPath: this.indexPath,
8200
9141
  currentBranch: this.currentBranch,
8201
9142
  baseBranch: this.baseBranch,
8202
- compatibility: this.indexCompatibility,
9143
+ compatibility,
8203
9144
  failedBatchesCount,
8204
9145
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8205
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9146
+ warning: warning || void 0
8206
9147
  };
8207
9148
  }
9149
+ async forceIndex(onProgress) {
9150
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9151
+ await this.ensureInitializedUnlocked(recoveredOwners);
9152
+ await this.clearIndexUnlocked();
9153
+ return this.indexUnlocked(onProgress, [], true);
9154
+ });
9155
+ }
8208
9156
  async clearIndex() {
8209
- const { store, invertedIndex, database } = await this.ensureInitialized();
9157
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9158
+ await this.ensureInitializedUnlocked(recoveredOwners);
9159
+ await this.clearIndexUnlocked();
9160
+ });
9161
+ }
9162
+ async clearIndexUnlocked() {
9163
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8210
9164
  if (this.config.scope === "global") {
8211
9165
  store.load();
8212
9166
  invertedIndex.load();
@@ -8233,7 +9187,7 @@ var Indexer = class {
8233
9187
  store.clear();
8234
9188
  store.save();
8235
9189
  invertedIndex.clear();
8236
- invertedIndex.save();
9190
+ this.saveInvertedIndex(invertedIndex);
8237
9191
  this.fileHashCache.clear();
8238
9192
  this.saveFileHashCache();
8239
9193
  database.clearAllIndexedData();
@@ -8257,14 +9211,7 @@ var Indexer = class {
8257
9211
  this.indexCompatibility = compatibility;
8258
9212
  return;
8259
9213
  }
8260
- const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8261
- if (this.host !== "opencode") {
8262
- localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8263
- }
8264
- const isLocalProjectIndex = localProjectIndexPaths.some(
8265
- (localPath) => path12.resolve(this.indexPath) === path12.resolve(localPath)
8266
- );
8267
- if (!isLocalProjectIndex) {
9214
+ if (!this.isLocalProjectIndexPath()) {
8268
9215
  throw new Error(
8269
9216
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8270
9217
  );
@@ -8272,7 +9219,7 @@ var Indexer = class {
8272
9219
  store.clear();
8273
9220
  store.save();
8274
9221
  invertedIndex.clear();
8275
- invertedIndex.save();
9222
+ this.saveInvertedIndex(invertedIndex);
8276
9223
  this.fileHashCache.clear();
8277
9224
  this.saveFileHashCache();
8278
9225
  database.clearAllIndexedData();
@@ -8290,7 +9237,13 @@ var Indexer = class {
8290
9237
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8291
9238
  }
8292
9239
  async healthCheck() {
8293
- const { store, invertedIndex, database } = await this.ensureInitialized();
9240
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9241
+ await this.ensureInitializedUnlocked(recoveredOwners);
9242
+ return this.healthCheckUnlocked();
9243
+ });
9244
+ }
9245
+ async healthCheckUnlocked() {
9246
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8294
9247
  this.logger.gc("info", "Starting health check");
8295
9248
  const allMetadata = store.getAllMetadata();
8296
9249
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8303,7 +9256,7 @@ var Indexer = class {
8303
9256
  const removedChunkKeys = [];
8304
9257
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8305
9258
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8306
- if (!existsSync7(filePath)) {
9259
+ if (!existsSync8(filePath)) {
8307
9260
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8308
9261
  for (const key of chunkKeys) {
8309
9262
  removedChunkKeys.push(key);
@@ -8328,7 +9281,7 @@ var Indexer = class {
8328
9281
  const removedCount = removedChunkKeys.length;
8329
9282
  if (removedCount > 0) {
8330
9283
  store.save();
8331
- invertedIndex.save();
9284
+ this.saveInvertedIndex(invertedIndex);
8332
9285
  }
8333
9286
  let gcOrphanEmbeddings;
8334
9287
  let gcOrphanChunks;
@@ -8343,7 +9296,7 @@ var Indexer = class {
8343
9296
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8344
9297
  throw error;
8345
9298
  }
8346
- await this.ensureInitialized();
9299
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8347
9300
  return {
8348
9301
  removed: 0,
8349
9302
  filePaths: [],
@@ -8352,7 +9305,7 @@ var Indexer = class {
8352
9305
  gcOrphanSymbols: 0,
8353
9306
  gcOrphanCallEdges: 0,
8354
9307
  resetCorruptedIndex: true,
8355
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9308
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8356
9309
  };
8357
9310
  }
8358
9311
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8365,7 +9318,13 @@ var Indexer = class {
8365
9318
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8366
9319
  }
8367
9320
  async retryFailedBatches() {
8368
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9321
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9322
+ await this.ensureInitializedUnlocked(recoveredOwners);
9323
+ return this.retryFailedBatchesUnlocked();
9324
+ });
9325
+ }
9326
+ async retryFailedBatchesUnlocked() {
9327
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8369
9328
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8370
9329
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8371
9330
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8529,7 +9488,7 @@ var Indexer = class {
8529
9488
  }
8530
9489
  if (succeeded > 0) {
8531
9490
  store.save();
8532
- invertedIndex.save();
9491
+ this.saveInvertedIndex(invertedIndex);
8533
9492
  }
8534
9493
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8535
9494
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8557,15 +9516,16 @@ var Indexer = class {
8557
9516
  }
8558
9517
  }
8559
9518
  async getDatabaseStats() {
8560
- const { database } = await this.ensureInitialized();
9519
+ const { database, readIssues } = await this.ensureInitialized();
9520
+ this.requireReadableComponents(readIssues, "database");
8561
9521
  return database.getStats();
8562
9522
  }
8563
9523
  getLogger() {
8564
9524
  return this.logger;
8565
9525
  }
8566
9526
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8567
- const { store, provider, database } = await this.ensureInitialized();
8568
- const compatibility = this.checkCompatibility();
9527
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9528
+ this.requireReadableComponents(readIssues, "vectors", "database");
8569
9529
  if (!compatibility.compatible) {
8570
9530
  throw new Error(
8571
9531
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8679,7 +9639,8 @@ var Indexer = class {
8679
9639
  );
8680
9640
  }
8681
9641
  async getCallers(targetName, callTypeFilter) {
8682
- const { database } = await this.ensureInitialized();
9642
+ const { database, readIssues } = await this.ensureInitialized();
9643
+ this.requireReadableComponents(readIssues, "database");
8683
9644
  const seen = /* @__PURE__ */ new Set();
8684
9645
  const results = [];
8685
9646
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8693,7 +9654,8 @@ var Indexer = class {
8693
9654
  return results;
8694
9655
  }
8695
9656
  async getCallees(symbolId, callTypeFilter) {
8696
- const { database } = await this.ensureInitialized();
9657
+ const { database, readIssues } = await this.ensureInitialized();
9658
+ this.requireReadableComponents(readIssues, "database");
8697
9659
  const seen = /* @__PURE__ */ new Set();
8698
9660
  const results = [];
8699
9661
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8707,43 +9669,50 @@ var Indexer = class {
8707
9669
  return results;
8708
9670
  }
8709
9671
  async findCallPath(fromName, toName, maxDepth) {
8710
- const { database } = await this.ensureInitialized();
9672
+ const { database, readIssues } = await this.ensureInitialized();
9673
+ this.requireReadableComponents(readIssues, "database");
8711
9674
  let shortest = [];
8712
9675
  for (const branchKey of this.getBranchCatalogKeys()) {
8713
- const path23 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8714
- if (path23.length > 0 && (shortest.length === 0 || path23.length < shortest.length)) {
8715
- shortest = path23;
9676
+ const path24 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9677
+ if (path24.length > 0 && (shortest.length === 0 || path24.length < shortest.length)) {
9678
+ shortest = path24;
8716
9679
  }
8717
9680
  }
8718
9681
  return shortest;
8719
9682
  }
8720
9683
  async getSymbolsForBranch(branch) {
8721
- const { database } = await this.ensureInitialized();
9684
+ const { database, readIssues } = await this.ensureInitialized();
9685
+ this.requireReadableComponents(readIssues, "database");
8722
9686
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8723
9687
  return database.getSymbolsForBranch(resolvedBranch);
8724
9688
  }
8725
9689
  async getSymbolsForFiles(filePaths, branch) {
8726
- const { database } = await this.ensureInitialized();
9690
+ const { database, readIssues } = await this.ensureInitialized();
9691
+ this.requireReadableComponents(readIssues, "database");
8727
9692
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8728
9693
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8729
9694
  }
8730
9695
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8731
- const { database } = await this.ensureInitialized();
9696
+ const { database, readIssues } = await this.ensureInitialized();
9697
+ this.requireReadableComponents(readIssues, "database");
8732
9698
  const branch = this.getBranchCatalogKey();
8733
9699
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8734
9700
  }
8735
9701
  async detectCommunities(branch, symbolIds) {
8736
- const { database } = await this.ensureInitialized();
9702
+ const { database, readIssues } = await this.ensureInitialized();
9703
+ this.requireReadableComponents(readIssues, "database");
8737
9704
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8738
9705
  return database.detectCommunities(resolvedBranch, symbolIds);
8739
9706
  }
8740
9707
  async computeCentrality(branch) {
8741
- const { database } = await this.ensureInitialized();
9708
+ const { database, readIssues } = await this.ensureInitialized();
9709
+ this.requireReadableComponents(readIssues, "database");
8742
9710
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8743
9711
  return database.computeCentrality(resolvedBranch);
8744
9712
  }
8745
9713
  async getPrImpact(opts) {
8746
- const { database } = await this.ensureInitialized();
9714
+ const { database, readIssues } = await this.ensureInitialized();
9715
+ this.requireReadableComponents(readIssues, "database");
8747
9716
  const execFileAsync3 = promisify3(execFile3);
8748
9717
  const changedFilesResult = await getChangedFiles({
8749
9718
  pr: opts.pr,
@@ -8766,7 +9735,7 @@ var Indexer = class {
8766
9735
  "Run index_codebase first to build the call graph and symbol index for this branch."
8767
9736
  );
8768
9737
  }
8769
- const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
9738
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8770
9739
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8771
9740
  const directIds = directSymbols.map((s) => s.id);
8772
9741
  const direction = opts.direction ?? "both";
@@ -8849,7 +9818,7 @@ var Indexer = class {
8849
9818
  projectRoot: this.projectRoot,
8850
9819
  baseBranch: this.baseBranch
8851
9820
  });
8852
- const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
9821
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8853
9822
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8854
9823
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8855
9824
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8899,7 +9868,8 @@ var Indexer = class {
8899
9868
  };
8900
9869
  }
8901
9870
  async getVisualizationData(options) {
8902
- const { database, store } = await this.ensureInitialized();
9871
+ const { database, store, readIssues } = await this.ensureInitialized();
9872
+ this.requireReadableComponents(readIssues, "vectors", "database");
8903
9873
  const seenSymbols = /* @__PURE__ */ new Map();
8904
9874
  const seenEdges = /* @__PURE__ */ new Map();
8905
9875
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8912,12 +9882,12 @@ var Indexer = class {
8912
9882
  if (meta.filePath) filePaths.add(meta.filePath);
8913
9883
  }
8914
9884
  const directory = options?.directory?.replace(/\/$/, "");
8915
- const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
9885
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8916
9886
  for (const filePath of filePaths) {
8917
9887
  if (directory) {
8918
- const absoluteFilePath = path12.resolve(filePath);
9888
+ const absoluteFilePath = path13.resolve(filePath);
8919
9889
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8920
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
9890
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8921
9891
  if (!matchesRelative && !matchesProjectRelative) {
8922
9892
  continue;
8923
9893
  }
@@ -8939,53 +9909,64 @@ var Indexer = class {
8939
9909
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8940
9910
  }
8941
9911
  async close() {
8942
- await this.database?.close();
9912
+ this.database?.close();
9913
+ for (const database of this.retiredDatabases) {
9914
+ database.close();
9915
+ }
9916
+ this.retiredDatabases = [];
8943
9917
  this.database = null;
8944
9918
  this.store = null;
8945
9919
  this.invertedIndex = null;
8946
9920
  this.provider = null;
8947
9921
  this.reranker = null;
9922
+ this.configuredProviderInfo = null;
9923
+ this.indexCompatibility = null;
9924
+ this.initializationMode = "none";
9925
+ this.readIssues = [];
9926
+ this.readerArtifactFingerprint = null;
9927
+ this.writerArtifactFingerprint = null;
9928
+ this.readerArtifactRetryAfter.clear();
8948
9929
  }
8949
9930
  };
8950
9931
 
8951
9932
  // src/tools/knowledge-base-paths.ts
8952
- import * as path13 from "path";
9933
+ import * as path14 from "path";
8953
9934
  function resolveConfigPathValue(value, baseDir) {
8954
9935
  const trimmed = value.trim();
8955
9936
  if (!trimmed) {
8956
9937
  return trimmed;
8957
9938
  }
8958
- const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
8959
- return path13.normalize(absolutePath);
9939
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9940
+ return path14.normalize(absolutePath);
8960
9941
  }
8961
9942
  function serializeConfigPathValue(value, baseDir) {
8962
9943
  const trimmed = value.trim();
8963
9944
  if (!trimmed) {
8964
9945
  return trimmed;
8965
9946
  }
8966
- if (!path13.isAbsolute(trimmed)) {
8967
- return normalizePathSeparators(path13.normalize(trimmed));
9947
+ if (!path14.isAbsolute(trimmed)) {
9948
+ return normalizePathSeparators(path14.normalize(trimmed));
8968
9949
  }
8969
- const relativePath = path13.relative(baseDir, trimmed);
8970
- if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
8971
- return normalizePathSeparators(path13.normalize(relativePath || "."));
9950
+ const relativePath = path14.relative(baseDir, trimmed);
9951
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9952
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8972
9953
  }
8973
- return path13.normalize(trimmed);
9954
+ return path14.normalize(trimmed);
8974
9955
  }
8975
9956
  function resolveKnowledgeBasePath(value, projectRoot) {
8976
- return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
9957
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
8977
9958
  }
8978
9959
  function normalizeKnowledgeBasePath2(value, projectRoot) {
8979
- return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
9960
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
8980
9961
  }
8981
9962
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
8982
- const normalizedInput = path13.normalize(inputPath);
9963
+ const normalizedInput = path14.normalize(inputPath);
8983
9964
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
8984
9965
  }
8985
9966
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
8986
- const normalizedInput = path13.normalize(inputPath);
9967
+ const normalizedInput = path14.normalize(inputPath);
8987
9968
  return knowledgeBases.findIndex(
8988
- (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
9969
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
8989
9970
  );
8990
9971
  }
8991
9972
 
@@ -9085,6 +10066,10 @@ function formatStatus(status) {
9085
10066
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
9086
10067
  }
9087
10068
  }
10069
+ if (status.warning) {
10070
+ lines.push("");
10071
+ lines.push(`INDEX WARNING: ${status.warning}`);
10072
+ }
9088
10073
  if (status.compatibility && !status.compatibility.compatible) {
9089
10074
  lines.push("");
9090
10075
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -9190,16 +10175,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
9190
10175
  return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
9191
10176
  }).join("\n");
9192
10177
  }
9193
- function formatCallGraphPath(from, to, path23) {
9194
- if (path23.length === 0) {
10178
+ function formatCallGraphPath(from, to, path24) {
10179
+ if (path24.length === 0) {
9195
10180
  return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
9196
10181
  }
9197
- const formatted = path23.map((hop, index) => {
10182
+ const formatted = path24.map((hop, index) => {
9198
10183
  const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
9199
10184
  const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
9200
10185
  return `${prefix} ${hop.symbolName}${location}`;
9201
10186
  });
9202
- return `Path (${path23.length} hops):
10187
+ return `Path (${path24.length} hops):
9203
10188
  ${formatted.join("\n")}`;
9204
10189
  }
9205
10190
  function formatResultHeader(result, index) {
@@ -9239,8 +10224,8 @@ ${truncateContent(r.content)}
9239
10224
  }
9240
10225
 
9241
10226
  // src/tools/config-state.ts
9242
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
9243
- import * as path14 from "path";
10227
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10228
+ import * as path15 from "path";
9244
10229
  function normalizeKnowledgeBasePaths(config, projectRoot) {
9245
10230
  const normalized = { ...config };
9246
10231
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -9267,10 +10252,10 @@ function loadEditableConfig(projectRoot, host = "opencode") {
9267
10252
  }
9268
10253
  function saveConfig(projectRoot, config, host = "opencode") {
9269
10254
  const configPath = getConfigPath(projectRoot, host);
9270
- const configDir = path14.dirname(configPath);
9271
- const configBaseDir = path14.dirname(configDir);
9272
- if (!existsSync8(configDir)) {
9273
- mkdirSync3(configDir, { recursive: true });
10255
+ const configDir = path15.dirname(configPath);
10256
+ const configBaseDir = path15.dirname(configDir);
10257
+ if (!existsSync9(configDir)) {
10258
+ mkdirSync4(configDir, { recursive: true });
9274
10259
  }
9275
10260
  const serializableConfig = { ...config };
9276
10261
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9278,13 +10263,31 @@ function saveConfig(projectRoot, config, host = "opencode") {
9278
10263
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9279
10264
  );
9280
10265
  }
9281
- writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10266
+ writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9282
10267
  }
9283
10268
 
9284
10269
  // src/tools/operations.ts
9285
10270
  var indexerCache = /* @__PURE__ */ new Map();
9286
10271
  var configCache = /* @__PURE__ */ new Map();
9287
10272
  var defaultProjectRoots = /* @__PURE__ */ new Map();
10273
+ function getIndexBusyResult(error) {
10274
+ if (!isIndexLockContentionError(error)) return null;
10275
+ const owner = error.owner;
10276
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10277
+ if (error.reason === "legacy-lock") {
10278
+ return {
10279
+ kind: "busy",
10280
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10281
+ };
10282
+ }
10283
+ if (error.reason === "unknown-owner") {
10284
+ return {
10285
+ kind: "busy",
10286
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10287
+ };
10288
+ }
10289
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10290
+ }
9288
10291
  function getProjectRoot(projectRoot, host) {
9289
10292
  if (projectRoot) {
9290
10293
  return projectRoot;
@@ -9329,8 +10332,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
9329
10332
  }
9330
10333
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
9331
10334
  const root = getProjectRoot(projectRoot, host);
9332
- const localIndexPath = path15.join(root, getHostProjectIndexRelativePath(host));
9333
- if (existsSync9(localIndexPath)) {
10335
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10336
+ if (existsSync10(localIndexPath)) {
9334
10337
  return false;
9335
10338
  }
9336
10339
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
@@ -9386,35 +10389,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
9386
10389
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
9387
10390
  const root = getProjectRoot(projectRoot, host);
9388
10391
  const key = getIndexerCacheKey(root, host);
9389
- const cachedConfig = configCache.get(key);
9390
10392
  let indexer = getIndexerForProject(root, host);
9391
- if (args.estimateOnly) {
9392
- return { kind: "estimate", estimate: await indexer.estimateCost() };
9393
- }
9394
- if (args.force) {
9395
- if (shouldForceLocalizeProjectIndex(root, host)) {
9396
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
9397
- refreshIndexerForDirectory(root, host, cachedConfig);
9398
- indexer = getIndexerForProject(root, host);
9399
- }
9400
- await indexer.clearIndex();
9401
- refreshIndexerForDirectory(root, host, cachedConfig);
9402
- indexer = getIndexerForProject(root, host);
9403
- }
9404
- const stats = await indexer.index((progress) => {
9405
- if (onProgress) {
9406
- return onProgress(formatProgressTitle(progress), {
9407
- phase: progress.phase,
9408
- filesProcessed: progress.filesProcessed,
9409
- totalFiles: progress.totalFiles,
9410
- chunksProcessed: progress.chunksProcessed,
9411
- totalChunks: progress.totalChunks,
9412
- percentage: calculatePercentage(progress)
10393
+ const runtimeConfig = configCache.get(key);
10394
+ try {
10395
+ if (args.estimateOnly) {
10396
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10397
+ }
10398
+ const runIndex = async (target) => {
10399
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10400
+ return operation((progress) => {
10401
+ if (onProgress) {
10402
+ return onProgress(formatProgressTitle(progress), {
10403
+ phase: progress.phase,
10404
+ filesProcessed: progress.filesProcessed,
10405
+ totalFiles: progress.totalFiles,
10406
+ chunksProcessed: progress.chunksProcessed,
10407
+ totalChunks: progress.totalChunks,
10408
+ percentage: calculatePercentage(progress)
10409
+ });
10410
+ }
10411
+ return Promise.resolve();
9413
10412
  });
10413
+ };
10414
+ let stats;
10415
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10416
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10417
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10418
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10419
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10420
+ indexer = getIndexerForProject(root, host);
10421
+ return runIndex(indexer);
10422
+ }, { completeRecoveries: false });
10423
+ } else {
10424
+ stats = await runIndex(indexer);
9414
10425
  }
9415
- return Promise.resolve();
9416
- });
9417
- return { kind: "stats", stats };
10426
+ return { kind: "stats", stats };
10427
+ } catch (error) {
10428
+ const busyResult = getIndexBusyResult(error);
10429
+ if (!busyResult) throw error;
10430
+ return busyResult;
10431
+ }
9418
10432
  }
9419
10433
  async function getIndexStatus(projectRoot, host) {
9420
10434
  const indexer = getIndexerForProject(projectRoot, host);
@@ -9424,6 +10438,15 @@ async function getIndexHealthCheck(projectRoot, host) {
9424
10438
  const indexer = getIndexerForProject(projectRoot, host);
9425
10439
  return indexer.healthCheck();
9426
10440
  }
10441
+ async function runIndexHealthCheck(projectRoot, host) {
10442
+ try {
10443
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
10444
+ } catch (error) {
10445
+ const busyResult = getIndexBusyResult(error);
10446
+ if (!busyResult) throw error;
10447
+ return busyResult;
10448
+ }
10449
+ }
9427
10450
  async function getIndexMetrics(projectRoot, host) {
9428
10451
  const indexer = getIndexerForProject(projectRoot, host);
9429
10452
  const logger = indexer.getLogger();
@@ -9479,15 +10502,15 @@ async function getIndexLogs(projectRoot, host, args) {
9479
10502
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9480
10503
  const root = getProjectRoot(projectRoot, host);
9481
10504
  const inputPath = knowledgeBasePath.trim();
9482
- const normalizedPath = path15.resolve(
9483
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10505
+ const normalizedPath = path16.resolve(
10506
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9484
10507
  );
9485
- if (!existsSync9(normalizedPath)) {
10508
+ if (!existsSync10(normalizedPath)) {
9486
10509
  return `Error: Directory does not exist: ${normalizedPath}`;
9487
10510
  }
9488
10511
  let realPath;
9489
10512
  try {
9490
- realPath = realpathSync(normalizedPath);
10513
+ realPath = realpathSync2(normalizedPath);
9491
10514
  } catch {
9492
10515
  return `Error: Cannot resolve path: ${normalizedPath}`;
9493
10516
  }
@@ -9516,13 +10539,13 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9516
10539
  }
9517
10540
  }
9518
10541
  for (const dotDir of sensitiveDotDirs) {
9519
- const sensitiveDir = path15.join(homeDir, dotDir);
10542
+ const sensitiveDir = path16.join(homeDir, dotDir);
9520
10543
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9521
10544
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9522
10545
  }
9523
10546
  }
9524
10547
  try {
9525
- const stat4 = statSync3(normalizedPath);
10548
+ const stat4 = statSync4(normalizedPath);
9526
10549
  if (!stat4.isDirectory()) {
9527
10550
  return `Error: Path is not a directory: ${normalizedPath}`;
9528
10551
  }
@@ -9562,7 +10585,7 @@ function listKnowledgeBases(projectRoot, host) {
9562
10585
  for (let i = 0; i < knowledgeBases.length; i++) {
9563
10586
  const kb = knowledgeBases[i];
9564
10587
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9565
- const exists = existsSync9(resolvedPath);
10588
+ const exists = existsSync10(resolvedPath);
9566
10589
  result += `[${i + 1}] ${kb}
9567
10590
  `;
9568
10591
  result += ` Resolved: ${resolvedPath}
@@ -9571,7 +10594,7 @@ function listKnowledgeBases(projectRoot, host) {
9571
10594
  `;
9572
10595
  if (exists) {
9573
10596
  try {
9574
- const stat4 = statSync3(resolvedPath);
10597
+ const stat4 = statSync4(resolvedPath);
9575
10598
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
9576
10599
  `;
9577
10600
  } catch {
@@ -9579,7 +10602,7 @@ function listKnowledgeBases(projectRoot, host) {
9579
10602
  }
9580
10603
  result += "\n";
9581
10604
  }
9582
- const hasHostConfig = existsSync9(path15.join(root, getHostProjectConfigRelativePath(host)));
10605
+ const hasHostConfig = existsSync10(path16.join(root, getHostProjectConfigRelativePath(host)));
9583
10606
  if (hasHostConfig) {
9584
10607
  result += `
9585
10608
  Config sources: 1 file(s).`;
@@ -9702,7 +10725,7 @@ var ReaddirpStream = class extends Readable {
9702
10725
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9703
10726
  const statMethod = opts.lstat ? lstat : stat;
9704
10727
  if (wantBigintFsStats) {
9705
- this._stat = (path23) => statMethod(path23, { bigint: true });
10728
+ this._stat = (path24) => statMethod(path24, { bigint: true });
9706
10729
  } else {
9707
10730
  this._stat = statMethod;
9708
10731
  }
@@ -9727,8 +10750,8 @@ var ReaddirpStream = class extends Readable {
9727
10750
  const par = this.parent;
9728
10751
  const fil = par && par.files;
9729
10752
  if (fil && fil.length > 0) {
9730
- const { path: path23, depth } = par;
9731
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
10753
+ const { path: path24, depth } = par;
10754
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
9732
10755
  const awaited = await Promise.all(slice);
9733
10756
  for (const entry of awaited) {
9734
10757
  if (!entry)
@@ -9768,20 +10791,20 @@ var ReaddirpStream = class extends Readable {
9768
10791
  this.reading = false;
9769
10792
  }
9770
10793
  }
9771
- async _exploreDir(path23, depth) {
10794
+ async _exploreDir(path24, depth) {
9772
10795
  let files;
9773
10796
  try {
9774
- files = await readdir(path23, this._rdOptions);
10797
+ files = await readdir(path24, this._rdOptions);
9775
10798
  } catch (error) {
9776
10799
  this._onError(error);
9777
10800
  }
9778
- return { files, depth, path: path23 };
10801
+ return { files, depth, path: path24 };
9779
10802
  }
9780
- async _formatEntry(dirent, path23) {
10803
+ async _formatEntry(dirent, path24) {
9781
10804
  let entry;
9782
10805
  const basename5 = this._isDirent ? dirent.name : dirent;
9783
10806
  try {
9784
- const fullPath = presolve(pjoin(path23, basename5));
10807
+ const fullPath = presolve(pjoin(path24, basename5));
9785
10808
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
9786
10809
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9787
10810
  } catch (err) {
@@ -10181,16 +11204,16 @@ var delFromSet = (main, prop, item) => {
10181
11204
  };
10182
11205
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
10183
11206
  var FsWatchInstances = /* @__PURE__ */ new Map();
10184
- function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
11207
+ function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
10185
11208
  const handleEvent = (rawEvent, evPath) => {
10186
- listener(path23);
10187
- emitRaw(rawEvent, evPath, { watchedPath: path23 });
10188
- if (evPath && path23 !== evPath) {
10189
- fsWatchBroadcast(sp.resolve(path23, evPath), KEY_LISTENERS, sp.join(path23, evPath));
11209
+ listener(path24);
11210
+ emitRaw(rawEvent, evPath, { watchedPath: path24 });
11211
+ if (evPath && path24 !== evPath) {
11212
+ fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
10190
11213
  }
10191
11214
  };
10192
11215
  try {
10193
- return fs_watch(path23, {
11216
+ return fs_watch(path24, {
10194
11217
  persistent: options.persistent
10195
11218
  }, handleEvent);
10196
11219
  } catch (error) {
@@ -10206,12 +11229,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
10206
11229
  listener(val1, val2, val3);
10207
11230
  });
10208
11231
  };
10209
- var setFsWatchListener = (path23, fullPath, options, handlers) => {
11232
+ var setFsWatchListener = (path24, fullPath, options, handlers) => {
10210
11233
  const { listener, errHandler, rawEmitter } = handlers;
10211
11234
  let cont = FsWatchInstances.get(fullPath);
10212
11235
  let watcher;
10213
11236
  if (!options.persistent) {
10214
- watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
11237
+ watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
10215
11238
  if (!watcher)
10216
11239
  return;
10217
11240
  return watcher.close.bind(watcher);
@@ -10222,7 +11245,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10222
11245
  addAndConvert(cont, KEY_RAW, rawEmitter);
10223
11246
  } else {
10224
11247
  watcher = createFsWatchInstance(
10225
- path23,
11248
+ path24,
10226
11249
  options,
10227
11250
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10228
11251
  errHandler,
@@ -10237,7 +11260,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10237
11260
  cont.watcherUnusable = true;
10238
11261
  if (isWindows && error.code === "EPERM") {
10239
11262
  try {
10240
- const fd = await open(path23, "r");
11263
+ const fd = await open(path24, "r");
10241
11264
  await fd.close();
10242
11265
  broadcastErr(error);
10243
11266
  } catch (err) {
@@ -10268,7 +11291,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10268
11291
  };
10269
11292
  };
10270
11293
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10271
- var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
11294
+ var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
10272
11295
  const { listener, rawEmitter } = handlers;
10273
11296
  let cont = FsWatchFileInstances.get(fullPath);
10274
11297
  const copts = cont && cont.options;
@@ -10290,7 +11313,7 @@ var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
10290
11313
  });
10291
11314
  const currmtime = curr.mtimeMs;
10292
11315
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10293
- foreach(cont.listeners, (listener2) => listener2(path23, curr));
11316
+ foreach(cont.listeners, (listener2) => listener2(path24, curr));
10294
11317
  }
10295
11318
  })
10296
11319
  };
@@ -10320,13 +11343,13 @@ var NodeFsHandler = class {
10320
11343
  * @param listener on fs change
10321
11344
  * @returns closer for the watcher instance
10322
11345
  */
10323
- _watchWithNodeFs(path23, listener) {
11346
+ _watchWithNodeFs(path24, listener) {
10324
11347
  const opts = this.fsw.options;
10325
- const directory = sp.dirname(path23);
10326
- const basename5 = sp.basename(path23);
11348
+ const directory = sp.dirname(path24);
11349
+ const basename5 = sp.basename(path24);
10327
11350
  const parent = this.fsw._getWatchedDir(directory);
10328
11351
  parent.add(basename5);
10329
- const absolutePath = sp.resolve(path23);
11352
+ const absolutePath = sp.resolve(path24);
10330
11353
  const options = {
10331
11354
  persistent: opts.persistent
10332
11355
  };
@@ -10336,12 +11359,12 @@ var NodeFsHandler = class {
10336
11359
  if (opts.usePolling) {
10337
11360
  const enableBin = opts.interval !== opts.binaryInterval;
10338
11361
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
10339
- closer = setFsWatchFileListener(path23, absolutePath, options, {
11362
+ closer = setFsWatchFileListener(path24, absolutePath, options, {
10340
11363
  listener,
10341
11364
  rawEmitter: this.fsw._emitRaw
10342
11365
  });
10343
11366
  } else {
10344
- closer = setFsWatchListener(path23, absolutePath, options, {
11367
+ closer = setFsWatchListener(path24, absolutePath, options, {
10345
11368
  listener,
10346
11369
  errHandler: this._boundHandleError,
10347
11370
  rawEmitter: this.fsw._emitRaw
@@ -10363,7 +11386,7 @@ var NodeFsHandler = class {
10363
11386
  let prevStats = stats;
10364
11387
  if (parent.has(basename5))
10365
11388
  return;
10366
- const listener = async (path23, newStats) => {
11389
+ const listener = async (path24, newStats) => {
10367
11390
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10368
11391
  return;
10369
11392
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10377,11 +11400,11 @@ var NodeFsHandler = class {
10377
11400
  this.fsw._emit(EV.CHANGE, file, newStats2);
10378
11401
  }
10379
11402
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10380
- this.fsw._closeFile(path23);
11403
+ this.fsw._closeFile(path24);
10381
11404
  prevStats = newStats2;
10382
11405
  const closer2 = this._watchWithNodeFs(file, listener);
10383
11406
  if (closer2)
10384
- this.fsw._addPathCloser(path23, closer2);
11407
+ this.fsw._addPathCloser(path24, closer2);
10385
11408
  } else {
10386
11409
  prevStats = newStats2;
10387
11410
  }
@@ -10413,7 +11436,7 @@ var NodeFsHandler = class {
10413
11436
  * @param item basename of this item
10414
11437
  * @returns true if no more processing is needed for this entry.
10415
11438
  */
10416
- async _handleSymlink(entry, directory, path23, item) {
11439
+ async _handleSymlink(entry, directory, path24, item) {
10417
11440
  if (this.fsw.closed) {
10418
11441
  return;
10419
11442
  }
@@ -10423,7 +11446,7 @@ var NodeFsHandler = class {
10423
11446
  this.fsw._incrReadyCount();
10424
11447
  let linkPath;
10425
11448
  try {
10426
- linkPath = await fsrealpath(path23);
11449
+ linkPath = await fsrealpath(path24);
10427
11450
  } catch (e) {
10428
11451
  this.fsw._emitReady();
10429
11452
  return true;
@@ -10433,12 +11456,12 @@ var NodeFsHandler = class {
10433
11456
  if (dir.has(item)) {
10434
11457
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10435
11458
  this.fsw._symlinkPaths.set(full, linkPath);
10436
- this.fsw._emit(EV.CHANGE, path23, entry.stats);
11459
+ this.fsw._emit(EV.CHANGE, path24, entry.stats);
10437
11460
  }
10438
11461
  } else {
10439
11462
  dir.add(item);
10440
11463
  this.fsw._symlinkPaths.set(full, linkPath);
10441
- this.fsw._emit(EV.ADD, path23, entry.stats);
11464
+ this.fsw._emit(EV.ADD, path24, entry.stats);
10442
11465
  }
10443
11466
  this.fsw._emitReady();
10444
11467
  return true;
@@ -10468,9 +11491,9 @@ var NodeFsHandler = class {
10468
11491
  return;
10469
11492
  }
10470
11493
  const item = entry.path;
10471
- let path23 = sp.join(directory, item);
11494
+ let path24 = sp.join(directory, item);
10472
11495
  current.add(item);
10473
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
11496
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
10474
11497
  return;
10475
11498
  }
10476
11499
  if (this.fsw.closed) {
@@ -10479,8 +11502,8 @@ var NodeFsHandler = class {
10479
11502
  }
10480
11503
  if (item === target || !target && !previous.has(item)) {
10481
11504
  this.fsw._incrReadyCount();
10482
- path23 = sp.join(dir, sp.relative(dir, path23));
10483
- this._addToNodeFs(path23, initialAdd, wh, depth + 1);
11505
+ path24 = sp.join(dir, sp.relative(dir, path24));
11506
+ this._addToNodeFs(path24, initialAdd, wh, depth + 1);
10484
11507
  }
10485
11508
  }).on(EV.ERROR, this._boundHandleError);
10486
11509
  return new Promise((resolve13, reject) => {
@@ -10549,13 +11572,13 @@ var NodeFsHandler = class {
10549
11572
  * @param depth Child path actually targeted for watch
10550
11573
  * @param target Child path actually targeted for watch
10551
11574
  */
10552
- async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
11575
+ async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
10553
11576
  const ready = this.fsw._emitReady;
10554
- if (this.fsw._isIgnored(path23) || this.fsw.closed) {
11577
+ if (this.fsw._isIgnored(path24) || this.fsw.closed) {
10555
11578
  ready();
10556
11579
  return false;
10557
11580
  }
10558
- const wh = this.fsw._getWatchHelpers(path23);
11581
+ const wh = this.fsw._getWatchHelpers(path24);
10559
11582
  if (priorWh) {
10560
11583
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10561
11584
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10571,8 +11594,8 @@ var NodeFsHandler = class {
10571
11594
  const follow = this.fsw.options.followSymlinks;
10572
11595
  let closer;
10573
11596
  if (stats.isDirectory()) {
10574
- const absPath = sp.resolve(path23);
10575
- const targetPath = follow ? await fsrealpath(path23) : path23;
11597
+ const absPath = sp.resolve(path24);
11598
+ const targetPath = follow ? await fsrealpath(path24) : path24;
10576
11599
  if (this.fsw.closed)
10577
11600
  return;
10578
11601
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10582,29 +11605,29 @@ var NodeFsHandler = class {
10582
11605
  this.fsw._symlinkPaths.set(absPath, targetPath);
10583
11606
  }
10584
11607
  } else if (stats.isSymbolicLink()) {
10585
- const targetPath = follow ? await fsrealpath(path23) : path23;
11608
+ const targetPath = follow ? await fsrealpath(path24) : path24;
10586
11609
  if (this.fsw.closed)
10587
11610
  return;
10588
11611
  const parent = sp.dirname(wh.watchPath);
10589
11612
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10590
11613
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10591
- closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
11614
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
10592
11615
  if (this.fsw.closed)
10593
11616
  return;
10594
11617
  if (targetPath !== void 0) {
10595
- this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
11618
+ this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
10596
11619
  }
10597
11620
  } else {
10598
11621
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10599
11622
  }
10600
11623
  ready();
10601
11624
  if (closer)
10602
- this.fsw._addPathCloser(path23, closer);
11625
+ this.fsw._addPathCloser(path24, closer);
10603
11626
  return false;
10604
11627
  } catch (error) {
10605
11628
  if (this.fsw._handleError(error)) {
10606
11629
  ready();
10607
- return path23;
11630
+ return path24;
10608
11631
  }
10609
11632
  }
10610
11633
  }
@@ -10647,24 +11670,24 @@ function createPattern(matcher) {
10647
11670
  }
10648
11671
  return () => false;
10649
11672
  }
10650
- function normalizePath(path23) {
10651
- if (typeof path23 !== "string")
11673
+ function normalizePath(path24) {
11674
+ if (typeof path24 !== "string")
10652
11675
  throw new Error("string expected");
10653
- path23 = sp2.normalize(path23);
10654
- path23 = path23.replace(/\\/g, "/");
11676
+ path24 = sp2.normalize(path24);
11677
+ path24 = path24.replace(/\\/g, "/");
10655
11678
  let prepend = false;
10656
- if (path23.startsWith("//"))
11679
+ if (path24.startsWith("//"))
10657
11680
  prepend = true;
10658
- path23 = path23.replace(DOUBLE_SLASH_RE, "/");
11681
+ path24 = path24.replace(DOUBLE_SLASH_RE, "/");
10659
11682
  if (prepend)
10660
- path23 = "/" + path23;
10661
- return path23;
11683
+ path24 = "/" + path24;
11684
+ return path24;
10662
11685
  }
10663
11686
  function matchPatterns(patterns, testString, stats) {
10664
- const path23 = normalizePath(testString);
11687
+ const path24 = normalizePath(testString);
10665
11688
  for (let index = 0; index < patterns.length; index++) {
10666
11689
  const pattern = patterns[index];
10667
- if (pattern(path23, stats)) {
11690
+ if (pattern(path24, stats)) {
10668
11691
  return true;
10669
11692
  }
10670
11693
  }
@@ -10702,19 +11725,19 @@ var toUnix = (string) => {
10702
11725
  }
10703
11726
  return str;
10704
11727
  };
10705
- var normalizePathToUnix = (path23) => toUnix(sp2.normalize(toUnix(path23)));
10706
- var normalizeIgnored = (cwd = "") => (path23) => {
10707
- if (typeof path23 === "string") {
10708
- return normalizePathToUnix(sp2.isAbsolute(path23) ? path23 : sp2.join(cwd, path23));
11728
+ var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
11729
+ var normalizeIgnored = (cwd = "") => (path24) => {
11730
+ if (typeof path24 === "string") {
11731
+ return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
10709
11732
  } else {
10710
- return path23;
11733
+ return path24;
10711
11734
  }
10712
11735
  };
10713
- var getAbsolutePath = (path23, cwd) => {
10714
- if (sp2.isAbsolute(path23)) {
10715
- return path23;
11736
+ var getAbsolutePath = (path24, cwd) => {
11737
+ if (sp2.isAbsolute(path24)) {
11738
+ return path24;
10716
11739
  }
10717
- return sp2.join(cwd, path23);
11740
+ return sp2.join(cwd, path24);
10718
11741
  };
10719
11742
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10720
11743
  var DirEntry = class {
@@ -10779,10 +11802,10 @@ var WatchHelper = class {
10779
11802
  dirParts;
10780
11803
  followSymlinks;
10781
11804
  statMethod;
10782
- constructor(path23, follow, fsw) {
11805
+ constructor(path24, follow, fsw) {
10783
11806
  this.fsw = fsw;
10784
- const watchPath = path23;
10785
- this.path = path23 = path23.replace(REPLACER_RE, "");
11807
+ const watchPath = path24;
11808
+ this.path = path24 = path24.replace(REPLACER_RE, "");
10786
11809
  this.watchPath = watchPath;
10787
11810
  this.fullWatchPath = sp2.resolve(watchPath);
10788
11811
  this.dirParts = [];
@@ -10922,20 +11945,20 @@ var FSWatcher = class extends EventEmitter2 {
10922
11945
  this._closePromise = void 0;
10923
11946
  let paths = unifyPaths(paths_);
10924
11947
  if (cwd) {
10925
- paths = paths.map((path23) => {
10926
- const absPath = getAbsolutePath(path23, cwd);
11948
+ paths = paths.map((path24) => {
11949
+ const absPath = getAbsolutePath(path24, cwd);
10927
11950
  return absPath;
10928
11951
  });
10929
11952
  }
10930
- paths.forEach((path23) => {
10931
- this._removeIgnoredPath(path23);
11953
+ paths.forEach((path24) => {
11954
+ this._removeIgnoredPath(path24);
10932
11955
  });
10933
11956
  this._userIgnored = void 0;
10934
11957
  if (!this._readyCount)
10935
11958
  this._readyCount = 0;
10936
11959
  this._readyCount += paths.length;
10937
- Promise.all(paths.map(async (path23) => {
10938
- const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
11960
+ Promise.all(paths.map(async (path24) => {
11961
+ const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
10939
11962
  if (res)
10940
11963
  this._emitReady();
10941
11964
  return res;
@@ -10957,17 +11980,17 @@ var FSWatcher = class extends EventEmitter2 {
10957
11980
  return this;
10958
11981
  const paths = unifyPaths(paths_);
10959
11982
  const { cwd } = this.options;
10960
- paths.forEach((path23) => {
10961
- if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
11983
+ paths.forEach((path24) => {
11984
+ if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
10962
11985
  if (cwd)
10963
- path23 = sp2.join(cwd, path23);
10964
- path23 = sp2.resolve(path23);
11986
+ path24 = sp2.join(cwd, path24);
11987
+ path24 = sp2.resolve(path24);
10965
11988
  }
10966
- this._closePath(path23);
10967
- this._addIgnoredPath(path23);
10968
- if (this._watched.has(path23)) {
11989
+ this._closePath(path24);
11990
+ this._addIgnoredPath(path24);
11991
+ if (this._watched.has(path24)) {
10969
11992
  this._addIgnoredPath({
10970
- path: path23,
11993
+ path: path24,
10971
11994
  recursive: true
10972
11995
  });
10973
11996
  }
@@ -11031,38 +12054,38 @@ var FSWatcher = class extends EventEmitter2 {
11031
12054
  * @param stats arguments to be passed with event
11032
12055
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
11033
12056
  */
11034
- async _emit(event, path23, stats) {
12057
+ async _emit(event, path24, stats) {
11035
12058
  if (this.closed)
11036
12059
  return;
11037
12060
  const opts = this.options;
11038
12061
  if (isWindows)
11039
- path23 = sp2.normalize(path23);
12062
+ path24 = sp2.normalize(path24);
11040
12063
  if (opts.cwd)
11041
- path23 = sp2.relative(opts.cwd, path23);
11042
- const args = [path23];
12064
+ path24 = sp2.relative(opts.cwd, path24);
12065
+ const args = [path24];
11043
12066
  if (stats != null)
11044
12067
  args.push(stats);
11045
12068
  const awf = opts.awaitWriteFinish;
11046
12069
  let pw;
11047
- if (awf && (pw = this._pendingWrites.get(path23))) {
12070
+ if (awf && (pw = this._pendingWrites.get(path24))) {
11048
12071
  pw.lastChange = /* @__PURE__ */ new Date();
11049
12072
  return this;
11050
12073
  }
11051
12074
  if (opts.atomic) {
11052
12075
  if (event === EVENTS.UNLINK) {
11053
- this._pendingUnlinks.set(path23, [event, ...args]);
12076
+ this._pendingUnlinks.set(path24, [event, ...args]);
11054
12077
  setTimeout(() => {
11055
- this._pendingUnlinks.forEach((entry, path24) => {
12078
+ this._pendingUnlinks.forEach((entry, path25) => {
11056
12079
  this.emit(...entry);
11057
12080
  this.emit(EVENTS.ALL, ...entry);
11058
- this._pendingUnlinks.delete(path24);
12081
+ this._pendingUnlinks.delete(path25);
11059
12082
  });
11060
12083
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
11061
12084
  return this;
11062
12085
  }
11063
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
12086
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
11064
12087
  event = EVENTS.CHANGE;
11065
- this._pendingUnlinks.delete(path23);
12088
+ this._pendingUnlinks.delete(path24);
11066
12089
  }
11067
12090
  }
11068
12091
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -11080,16 +12103,16 @@ var FSWatcher = class extends EventEmitter2 {
11080
12103
  this.emitWithAll(event, args);
11081
12104
  }
11082
12105
  };
11083
- this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
12106
+ this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
11084
12107
  return this;
11085
12108
  }
11086
12109
  if (event === EVENTS.CHANGE) {
11087
- const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
12110
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
11088
12111
  if (isThrottled)
11089
12112
  return this;
11090
12113
  }
11091
12114
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
11092
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path23) : path23;
12115
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
11093
12116
  let stats2;
11094
12117
  try {
11095
12118
  stats2 = await stat3(fullPath);
@@ -11120,23 +12143,23 @@ var FSWatcher = class extends EventEmitter2 {
11120
12143
  * @param timeout duration of time to suppress duplicate actions
11121
12144
  * @returns tracking object or false if action should be suppressed
11122
12145
  */
11123
- _throttle(actionType, path23, timeout) {
12146
+ _throttle(actionType, path24, timeout) {
11124
12147
  if (!this._throttled.has(actionType)) {
11125
12148
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
11126
12149
  }
11127
12150
  const action = this._throttled.get(actionType);
11128
12151
  if (!action)
11129
12152
  throw new Error("invalid throttle");
11130
- const actionPath = action.get(path23);
12153
+ const actionPath = action.get(path24);
11131
12154
  if (actionPath) {
11132
12155
  actionPath.count++;
11133
12156
  return false;
11134
12157
  }
11135
12158
  let timeoutObject;
11136
12159
  const clear = () => {
11137
- const item = action.get(path23);
12160
+ const item = action.get(path24);
11138
12161
  const count = item ? item.count : 0;
11139
- action.delete(path23);
12162
+ action.delete(path24);
11140
12163
  clearTimeout(timeoutObject);
11141
12164
  if (item)
11142
12165
  clearTimeout(item.timeoutObject);
@@ -11144,7 +12167,7 @@ var FSWatcher = class extends EventEmitter2 {
11144
12167
  };
11145
12168
  timeoutObject = setTimeout(clear, timeout);
11146
12169
  const thr = { timeoutObject, clear, count: 0 };
11147
- action.set(path23, thr);
12170
+ action.set(path24, thr);
11148
12171
  return thr;
11149
12172
  }
11150
12173
  _incrReadyCount() {
@@ -11158,44 +12181,44 @@ var FSWatcher = class extends EventEmitter2 {
11158
12181
  * @param event
11159
12182
  * @param awfEmit Callback to be called when ready for event to be emitted.
11160
12183
  */
11161
- _awaitWriteFinish(path23, threshold, event, awfEmit) {
12184
+ _awaitWriteFinish(path24, threshold, event, awfEmit) {
11162
12185
  const awf = this.options.awaitWriteFinish;
11163
12186
  if (typeof awf !== "object")
11164
12187
  return;
11165
12188
  const pollInterval = awf.pollInterval;
11166
12189
  let timeoutHandler;
11167
- let fullPath = path23;
11168
- if (this.options.cwd && !sp2.isAbsolute(path23)) {
11169
- fullPath = sp2.join(this.options.cwd, path23);
12190
+ let fullPath = path24;
12191
+ if (this.options.cwd && !sp2.isAbsolute(path24)) {
12192
+ fullPath = sp2.join(this.options.cwd, path24);
11170
12193
  }
11171
12194
  const now = /* @__PURE__ */ new Date();
11172
12195
  const writes = this._pendingWrites;
11173
12196
  function awaitWriteFinishFn(prevStat) {
11174
12197
  statcb(fullPath, (err, curStat) => {
11175
- if (err || !writes.has(path23)) {
12198
+ if (err || !writes.has(path24)) {
11176
12199
  if (err && err.code !== "ENOENT")
11177
12200
  awfEmit(err);
11178
12201
  return;
11179
12202
  }
11180
12203
  const now2 = Number(/* @__PURE__ */ new Date());
11181
12204
  if (prevStat && curStat.size !== prevStat.size) {
11182
- writes.get(path23).lastChange = now2;
12205
+ writes.get(path24).lastChange = now2;
11183
12206
  }
11184
- const pw = writes.get(path23);
12207
+ const pw = writes.get(path24);
11185
12208
  const df = now2 - pw.lastChange;
11186
12209
  if (df >= threshold) {
11187
- writes.delete(path23);
12210
+ writes.delete(path24);
11188
12211
  awfEmit(void 0, curStat);
11189
12212
  } else {
11190
12213
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
11191
12214
  }
11192
12215
  });
11193
12216
  }
11194
- if (!writes.has(path23)) {
11195
- writes.set(path23, {
12217
+ if (!writes.has(path24)) {
12218
+ writes.set(path24, {
11196
12219
  lastChange: now,
11197
12220
  cancelWait: () => {
11198
- writes.delete(path23);
12221
+ writes.delete(path24);
11199
12222
  clearTimeout(timeoutHandler);
11200
12223
  return event;
11201
12224
  }
@@ -11206,8 +12229,8 @@ var FSWatcher = class extends EventEmitter2 {
11206
12229
  /**
11207
12230
  * Determines whether user has asked to ignore this path.
11208
12231
  */
11209
- _isIgnored(path23, stats) {
11210
- if (this.options.atomic && DOT_RE.test(path23))
12232
+ _isIgnored(path24, stats) {
12233
+ if (this.options.atomic && DOT_RE.test(path24))
11211
12234
  return true;
11212
12235
  if (!this._userIgnored) {
11213
12236
  const { cwd } = this.options;
@@ -11217,17 +12240,17 @@ var FSWatcher = class extends EventEmitter2 {
11217
12240
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11218
12241
  this._userIgnored = anymatch(list, void 0);
11219
12242
  }
11220
- return this._userIgnored(path23, stats);
12243
+ return this._userIgnored(path24, stats);
11221
12244
  }
11222
- _isntIgnored(path23, stat4) {
11223
- return !this._isIgnored(path23, stat4);
12245
+ _isntIgnored(path24, stat4) {
12246
+ return !this._isIgnored(path24, stat4);
11224
12247
  }
11225
12248
  /**
11226
12249
  * Provides a set of common helpers and properties relating to symlink handling.
11227
12250
  * @param path file or directory pattern being watched
11228
12251
  */
11229
- _getWatchHelpers(path23) {
11230
- return new WatchHelper(path23, this.options.followSymlinks, this);
12252
+ _getWatchHelpers(path24) {
12253
+ return new WatchHelper(path24, this.options.followSymlinks, this);
11231
12254
  }
11232
12255
  // Directory helpers
11233
12256
  // -----------------
@@ -11259,63 +12282,63 @@ var FSWatcher = class extends EventEmitter2 {
11259
12282
  * @param item base path of item/directory
11260
12283
  */
11261
12284
  _remove(directory, item, isDirectory) {
11262
- const path23 = sp2.join(directory, item);
11263
- const fullPath = sp2.resolve(path23);
11264
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
11265
- if (!this._throttle("remove", path23, 100))
12285
+ const path24 = sp2.join(directory, item);
12286
+ const fullPath = sp2.resolve(path24);
12287
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
12288
+ if (!this._throttle("remove", path24, 100))
11266
12289
  return;
11267
12290
  if (!isDirectory && this._watched.size === 1) {
11268
12291
  this.add(directory, item, true);
11269
12292
  }
11270
- const wp = this._getWatchedDir(path23);
12293
+ const wp = this._getWatchedDir(path24);
11271
12294
  const nestedDirectoryChildren = wp.getChildren();
11272
- nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
12295
+ nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
11273
12296
  const parent = this._getWatchedDir(directory);
11274
12297
  const wasTracked = parent.has(item);
11275
12298
  parent.remove(item);
11276
12299
  if (this._symlinkPaths.has(fullPath)) {
11277
12300
  this._symlinkPaths.delete(fullPath);
11278
12301
  }
11279
- let relPath = path23;
12302
+ let relPath = path24;
11280
12303
  if (this.options.cwd)
11281
- relPath = sp2.relative(this.options.cwd, path23);
12304
+ relPath = sp2.relative(this.options.cwd, path24);
11282
12305
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11283
12306
  const event = this._pendingWrites.get(relPath).cancelWait();
11284
12307
  if (event === EVENTS.ADD)
11285
12308
  return;
11286
12309
  }
11287
- this._watched.delete(path23);
12310
+ this._watched.delete(path24);
11288
12311
  this._watched.delete(fullPath);
11289
12312
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11290
- if (wasTracked && !this._isIgnored(path23))
11291
- this._emit(eventName, path23);
11292
- this._closePath(path23);
12313
+ if (wasTracked && !this._isIgnored(path24))
12314
+ this._emit(eventName, path24);
12315
+ this._closePath(path24);
11293
12316
  }
11294
12317
  /**
11295
12318
  * Closes all watchers for a path
11296
12319
  */
11297
- _closePath(path23) {
11298
- this._closeFile(path23);
11299
- const dir = sp2.dirname(path23);
11300
- this._getWatchedDir(dir).remove(sp2.basename(path23));
12320
+ _closePath(path24) {
12321
+ this._closeFile(path24);
12322
+ const dir = sp2.dirname(path24);
12323
+ this._getWatchedDir(dir).remove(sp2.basename(path24));
11301
12324
  }
11302
12325
  /**
11303
12326
  * Closes only file-specific watchers
11304
12327
  */
11305
- _closeFile(path23) {
11306
- const closers = this._closers.get(path23);
12328
+ _closeFile(path24) {
12329
+ const closers = this._closers.get(path24);
11307
12330
  if (!closers)
11308
12331
  return;
11309
12332
  closers.forEach((closer) => closer());
11310
- this._closers.delete(path23);
12333
+ this._closers.delete(path24);
11311
12334
  }
11312
- _addPathCloser(path23, closer) {
12335
+ _addPathCloser(path24, closer) {
11313
12336
  if (!closer)
11314
12337
  return;
11315
- let list = this._closers.get(path23);
12338
+ let list = this._closers.get(path24);
11316
12339
  if (!list) {
11317
12340
  list = [];
11318
- this._closers.set(path23, list);
12341
+ this._closers.set(path24, list);
11319
12342
  }
11320
12343
  list.push(closer);
11321
12344
  }
@@ -11345,7 +12368,7 @@ function watch(paths, options = {}) {
11345
12368
  var chokidar_default = { watch, FSWatcher };
11346
12369
 
11347
12370
  // src/watcher/file-watcher.ts
11348
- import * as path16 from "path";
12371
+ import * as path17 from "path";
11349
12372
  var FileWatcher = class {
11350
12373
  watcher = null;
11351
12374
  projectRoot;
@@ -11356,6 +12379,8 @@ var FileWatcher = class {
11356
12379
  debounceTimer = null;
11357
12380
  debounceMs = 1e3;
11358
12381
  onChanges = null;
12382
+ readyPromise = null;
12383
+ resolveReady = null;
11359
12384
  constructor(projectRoot, config, host = "opencode", options = {}) {
11360
12385
  this.projectRoot = projectRoot;
11361
12386
  this.config = config;
@@ -11371,15 +12396,15 @@ var FileWatcher = class {
11371
12396
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
11372
12397
  this.watcher = chokidar_default.watch(watchTargets, {
11373
12398
  ignored: (filePath) => {
11374
- const relativePath = path16.relative(this.projectRoot, filePath);
12399
+ const relativePath = path17.relative(this.projectRoot, filePath);
11375
12400
  if (!relativePath) return false;
11376
12401
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
11377
12402
  return false;
11378
12403
  }
11379
- if (hasFilteredPathSegment(relativePath, path16.sep)) {
12404
+ if (hasFilteredPathSegment(relativePath, path17.sep)) {
11380
12405
  return true;
11381
12406
  }
11382
- if (isRestrictedDirectory(relativePath, path16.sep)) {
12407
+ if (isRestrictedDirectory(relativePath, path17.sep)) {
11383
12408
  return true;
11384
12409
  }
11385
12410
  if (ignoreFilter.ignores(relativePath)) {
@@ -11394,6 +12419,13 @@ var FileWatcher = class {
11394
12419
  pollInterval: 100
11395
12420
  }
11396
12421
  });
12422
+ this.readyPromise = new Promise((resolve13) => {
12423
+ this.resolveReady = resolve13;
12424
+ });
12425
+ this.watcher.once("ready", () => {
12426
+ this.resolveReady?.();
12427
+ this.resolveReady = null;
12428
+ });
11397
12429
  this.watcher.on("error", (error) => {
11398
12430
  const err = error instanceof Error ? error : null;
11399
12431
  if (err?.code === "EPERM" || err?.code === "EACCES") {
@@ -11425,24 +12457,24 @@ var FileWatcher = class {
11425
12457
  this.scheduleFlush();
11426
12458
  }
11427
12459
  isProjectConfigPath(filePath) {
11428
- const relativePath = path16.relative(this.projectRoot, filePath);
11429
- const normalizedRelativePath = path16.normalize(relativePath);
12460
+ const relativePath = path17.relative(this.projectRoot, filePath);
12461
+ const normalizedRelativePath = path17.normalize(relativePath);
11430
12462
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
11431
12463
  }
11432
12464
  isProjectConfigPathOrAncestor(relativePath) {
11433
- const normalizedRelativePath = path16.normalize(relativePath);
12465
+ const normalizedRelativePath = path17.normalize(relativePath);
11434
12466
  return this.getProjectConfigRelativePaths().some(
11435
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path16.sep}`)
12467
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path17.sep}`)
11436
12468
  );
11437
12469
  }
11438
12470
  getProjectConfigRelativePaths() {
11439
12471
  if (this.configPath) {
11440
- return [path16.normalize(path16.relative(this.projectRoot, this.configPath))];
12472
+ return [path17.normalize(path17.relative(this.projectRoot, this.configPath))];
11441
12473
  }
11442
12474
  return [
11443
12475
  resolveProjectConfigPath(this.projectRoot, this.host),
11444
12476
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
11445
- ].map((configPath) => path16.normalize(path16.relative(this.projectRoot, configPath)));
12477
+ ].map((configPath) => path17.normalize(path17.relative(this.projectRoot, configPath)));
11446
12478
  }
11447
12479
  scheduleFlush() {
11448
12480
  if (this.debounceTimer) {
@@ -11457,7 +12489,7 @@ var FileWatcher = class {
11457
12489
  return;
11458
12490
  }
11459
12491
  const changes = Array.from(this.pendingChanges.entries()).map(
11460
- ([path23, type]) => ({ path: path23, type })
12492
+ ([path24, type]) => ({ path: path24, type })
11461
12493
  );
11462
12494
  this.pendingChanges.clear();
11463
12495
  try {
@@ -11466,25 +12498,32 @@ var FileWatcher = class {
11466
12498
  console.error("Error handling file changes:", error);
11467
12499
  }
11468
12500
  }
11469
- stop() {
12501
+ async stop() {
11470
12502
  if (this.debounceTimer) {
11471
12503
  clearTimeout(this.debounceTimer);
11472
12504
  this.debounceTimer = null;
11473
12505
  }
11474
12506
  if (this.watcher) {
11475
- this.watcher.close();
12507
+ const watcher = this.watcher;
11476
12508
  this.watcher = null;
12509
+ await watcher.close();
11477
12510
  }
12511
+ this.resolveReady?.();
12512
+ this.resolveReady = null;
12513
+ this.readyPromise = null;
11478
12514
  this.pendingChanges.clear();
11479
12515
  this.onChanges = null;
11480
12516
  }
11481
12517
  isRunning() {
11482
12518
  return this.watcher !== null;
11483
12519
  }
12520
+ async waitUntilReady() {
12521
+ await (this.readyPromise ?? Promise.resolve());
12522
+ }
11484
12523
  };
11485
12524
 
11486
12525
  // src/watcher/git-head-watcher.ts
11487
- import * as path17 from "path";
12526
+ import * as path18 from "path";
11488
12527
  var GitHeadWatcher = class {
11489
12528
  watcher = null;
11490
12529
  projectRoot;
@@ -11506,7 +12545,7 @@ var GitHeadWatcher = class {
11506
12545
  this.onBranchChange = handler;
11507
12546
  this.currentBranch = getCurrentBranch(this.projectRoot);
11508
12547
  const headPath = getHeadPath(this.projectRoot);
11509
- const refsPath = path17.join(this.projectRoot, ".git", "refs", "heads");
12548
+ const refsPath = path18.join(this.projectRoot, ".git", "refs", "heads");
11510
12549
  this.watcher = chokidar_default.watch([headPath, refsPath], {
11511
12550
  persistent: true,
11512
12551
  ignoreInitial: true,
@@ -11543,14 +12582,15 @@ var GitHeadWatcher = class {
11543
12582
  getCurrentBranch() {
11544
12583
  return this.currentBranch;
11545
12584
  }
11546
- stop() {
12585
+ async stop() {
11547
12586
  if (this.debounceTimer) {
11548
12587
  clearTimeout(this.debounceTimer);
11549
12588
  this.debounceTimer = null;
11550
12589
  }
11551
12590
  if (this.watcher) {
11552
- this.watcher.close();
12591
+ const watcher = this.watcher;
11553
12592
  this.watcher = null;
12593
+ await watcher.close();
11554
12594
  }
11555
12595
  this.onBranchChange = null;
11556
12596
  }
@@ -11568,6 +12608,8 @@ var BackgroundReindexer = class {
11568
12608
  running = false;
11569
12609
  pending = false;
11570
12610
  stopped = false;
12611
+ retryTimer = null;
12612
+ retryDelayMs = 50;
11571
12613
  request() {
11572
12614
  if (this.stopped) {
11573
12615
  return;
@@ -11578,9 +12620,13 @@ var BackgroundReindexer = class {
11578
12620
  stop() {
11579
12621
  this.stopped = true;
11580
12622
  this.pending = false;
12623
+ if (this.retryTimer) {
12624
+ clearTimeout(this.retryTimer);
12625
+ this.retryTimer = null;
12626
+ }
11581
12627
  }
11582
12628
  drain() {
11583
- if (this.stopped || this.running || !this.pending) {
12629
+ if (this.stopped || this.running || this.retryTimer || !this.pending) {
11584
12630
  return;
11585
12631
  }
11586
12632
  this.pending = false;
@@ -11588,13 +12634,29 @@ var BackgroundReindexer = class {
11588
12634
  void this.run();
11589
12635
  }
11590
12636
  async run() {
12637
+ let retryAfterContention = false;
11591
12638
  try {
11592
12639
  await this.runIndex();
12640
+ this.retryDelayMs = 50;
11593
12641
  } catch (error) {
11594
- console.error("[codebase-index] Background reindex failed:", error);
12642
+ if (isTransientIndexLockContention(error)) {
12643
+ this.pending = true;
12644
+ retryAfterContention = true;
12645
+ } else {
12646
+ console.error("[codebase-index] Background reindex failed:", error);
12647
+ }
11595
12648
  } finally {
11596
12649
  this.running = false;
11597
- this.drain();
12650
+ if (retryAfterContention && !this.stopped) {
12651
+ const delay = this.retryDelayMs;
12652
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
12653
+ this.retryTimer = setTimeout(() => {
12654
+ this.retryTimer = null;
12655
+ this.drain();
12656
+ }, delay);
12657
+ } else {
12658
+ this.drain();
12659
+ }
11598
12660
  }
11599
12661
  }
11600
12662
  };
@@ -11628,10 +12690,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
11628
12690
  return {
11629
12691
  fileWatcher,
11630
12692
  gitWatcher,
11631
- stop() {
12693
+ whenReady() {
12694
+ return fileWatcher.waitUntilReady();
12695
+ },
12696
+ async stop() {
11632
12697
  backgroundReindexer.stop();
11633
- fileWatcher.stop();
11634
- gitWatcher?.stop();
12698
+ await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
11635
12699
  }
11636
12700
  };
11637
12701
  }
@@ -11737,13 +12801,13 @@ var pr_impact = tool({
11737
12801
  });
11738
12802
 
11739
12803
  // src/tools/index.ts
11740
- import { writeFileSync as writeFileSync4 } from "fs";
11741
- import * as os4 from "os";
11742
- import * as path20 from "path";
12804
+ import { writeFileSync as writeFileSync5 } from "fs";
12805
+ import * as os5 from "os";
12806
+ import * as path21 from "path";
11743
12807
 
11744
12808
  // src/tools/visualize/activity.ts
11745
12809
  import { execFileSync } from "child_process";
11746
- import * as path18 from "path";
12810
+ import * as path19 from "path";
11747
12811
  function attachRecentActivity(data, projectRoot) {
11748
12812
  const activity = readGitActivity(projectRoot);
11749
12813
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -11905,7 +12969,7 @@ function normalizePath2(filePath) {
11905
12969
  return filePath.replace(/\\/g, "/");
11906
12970
  }
11907
12971
  function toGitRelativePath(projectRoot, filePath) {
11908
- const relativePath = path18.isAbsolute(filePath) ? path18.relative(projectRoot, filePath) : filePath;
12972
+ const relativePath = path19.isAbsolute(filePath) ? path19.relative(projectRoot, filePath) : filePath;
11909
12973
  return normalizePath2(relativePath);
11910
12974
  }
11911
12975
 
@@ -12163,7 +13227,7 @@ render();
12163
13227
  }
12164
13228
 
12165
13229
  // src/tools/visualize/transform.ts
12166
- import * as path19 from "path";
13230
+ import * as path20 from "path";
12167
13231
 
12168
13232
  // src/tools/visualize/modules.ts
12169
13233
  var MAX_MODULES = 18;
@@ -12423,7 +13487,7 @@ function transformForVisualization(symbols, edges, options = {}) {
12423
13487
  filePath: s.filePath,
12424
13488
  kind: s.kind,
12425
13489
  line: s.startLine,
12426
- directory: path19.dirname(s.filePath),
13490
+ directory: path20.dirname(s.filePath),
12427
13491
  moduleId: "",
12428
13492
  moduleLabel: ""
12429
13493
  }));
@@ -12504,7 +13568,9 @@ var index_codebase = tool2({
12504
13568
  const result = await runIndexCodebase(context?.worktree, DEFAULT_HOST, args, (title, metadata) => {
12505
13569
  context.metadata({ title, metadata });
12506
13570
  });
12507
- return result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
13571
+ if (result.kind === "estimate") return formatCostEstimate(result.estimate);
13572
+ if (result.kind === "busy") return result.text;
13573
+ return formatIndexStats(result.stats, args.verbose ?? false);
12508
13574
  }
12509
13575
  });
12510
13576
  var index_status = tool2({
@@ -12518,7 +13584,9 @@ var index_health_check = tool2({
12518
13584
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
12519
13585
  args: {},
12520
13586
  async execute(_args, context) {
12521
- return formatHealthCheck(await getIndexHealthCheck(context?.worktree, DEFAULT_HOST));
13587
+ const result = await runIndexHealthCheck(context?.worktree, DEFAULT_HOST);
13588
+ if (result.kind === "busy") return result.text;
13589
+ return formatHealthCheck(result.health);
12522
13590
  }
12523
13591
  });
12524
13592
  var index_metrics = tool2({
@@ -12638,8 +13706,8 @@ var call_graph_path = tool2({
12638
13706
  maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12639
13707
  },
12640
13708
  async execute(args, context) {
12641
- const path23 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
12642
- return formatCallGraphPath(args.from, args.to, path23);
13709
+ const path24 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
13710
+ return formatCallGraphPath(args.from, args.to, path24);
12643
13711
  }
12644
13712
  });
12645
13713
  var add_knowledge_base = tool2({
@@ -12692,8 +13760,8 @@ var index_visualize = tool2({
12692
13760
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
12693
13761
  }
12694
13762
  const html = generateVisualizationHtml(vizData);
12695
- const outputPath = path20.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12696
- writeFileSync4(outputPath, html, "utf-8");
13763
+ const outputPath = path21.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
13764
+ writeFileSync5(outputPath, html, "utf-8");
12697
13765
  let result = `Temporal call graph visualization generated: ${outputPath}
12698
13766
 
12699
13767
  `;
@@ -12714,8 +13782,8 @@ var index_visualize = tool2({
12714
13782
  });
12715
13783
 
12716
13784
  // src/commands/loader.ts
12717
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
12718
- import * as path21 from "path";
13785
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
13786
+ import * as path22 from "path";
12719
13787
  function parseFrontmatter(content) {
12720
13788
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12721
13789
  const match = content.match(frontmatterRegex);
@@ -12736,21 +13804,21 @@ function parseFrontmatter(content) {
12736
13804
  }
12737
13805
  function loadCommandsFromDirectory(commandsDir) {
12738
13806
  const commands = /* @__PURE__ */ new Map();
12739
- if (!existsSync10(commandsDir)) {
13807
+ if (!existsSync11(commandsDir)) {
12740
13808
  return commands;
12741
13809
  }
12742
- const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
13810
+ const files = readdirSync3(commandsDir).filter((f) => f.endsWith(".md"));
12743
13811
  for (const file of files) {
12744
- const filePath = path21.join(commandsDir, file);
13812
+ const filePath = path22.join(commandsDir, file);
12745
13813
  let content;
12746
13814
  try {
12747
- content = readFileSync7(filePath, "utf-8");
13815
+ content = readFileSync8(filePath, "utf-8");
12748
13816
  } catch (error) {
12749
13817
  const message = error instanceof Error ? error.message : String(error);
12750
13818
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
12751
13819
  }
12752
13820
  const { frontmatter, body } = parseFrontmatter(content);
12753
- const name = path21.basename(file, ".md");
13821
+ const name = path22.basename(file, ".md");
12754
13822
  const description = frontmatter.description || `Run the ${name} command`;
12755
13823
  commands.set(name, {
12756
13824
  description,
@@ -13054,18 +14122,37 @@ var RoutingHintController = class {
13054
14122
  };
13055
14123
 
13056
14124
  // src/utils/auto-index.ts
14125
+ var INITIAL_RETRY_DELAY_MS = 50;
14126
+ var MAX_RETRY_DELAY_MS = 500;
14127
+ var autoIndexStates = /* @__PURE__ */ new WeakMap();
13057
14128
  function getErrorMessage3(error) {
13058
14129
  return error instanceof Error ? error.message : String(error);
13059
14130
  }
13060
- function startAutoIndex(indexer, projectRoot) {
13061
- indexer.initialize().then(() => {
13062
- indexer.index().catch((error) => {
13063
- console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
13064
- });
14131
+ function runAutoIndex(indexer, projectRoot, state) {
14132
+ void indexer.index().then(() => {
14133
+ autoIndexStates.delete(indexer);
13065
14134
  }).catch((error) => {
13066
- console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14135
+ if (!isTransientIndexLockContention(error)) {
14136
+ autoIndexStates.delete(indexer);
14137
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14138
+ return;
14139
+ }
14140
+ const retryDelayMs = state.retryDelayMs;
14141
+ state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
14142
+ const retryTimer = setTimeout(() => {
14143
+ runAutoIndex(indexer, projectRoot, state);
14144
+ }, retryDelayMs);
14145
+ retryTimer.unref?.();
13067
14146
  });
13068
14147
  }
14148
+ function startAutoIndex(indexer, projectRoot) {
14149
+ if (autoIndexStates.has(indexer)) return;
14150
+ const state = {
14151
+ retryDelayMs: INITIAL_RETRY_DELAY_MS
14152
+ };
14153
+ autoIndexStates.set(indexer, state);
14154
+ runAutoIndex(indexer, projectRoot, state);
14155
+ }
13069
14156
 
13070
14157
  // src/index.ts
13071
14158
  var activeWatchers = /* @__PURE__ */ new Map();
@@ -13082,9 +14169,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
13082
14169
  function getCommandsDir() {
13083
14170
  let currentDir = process.cwd();
13084
14171
  if (typeof import.meta !== "undefined" && import.meta.url) {
13085
- currentDir = path22.dirname(fileURLToPath2(import.meta.url));
14172
+ currentDir = path23.dirname(fileURLToPath2(import.meta.url));
13086
14173
  }
13087
- return path22.join(currentDir, "..", "commands");
14174
+ return path23.join(currentDir, "..", "commands");
13088
14175
  }
13089
14176
  function appendRoutingHints(output, hints, preferredRole) {
13090
14177
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -13104,7 +14191,7 @@ var plugin = async ({ directory, worktree }) => {
13104
14191
  initializeTools2(projectRoot, config);
13105
14192
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
13106
14193
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
13107
- const isHomeDir = path22.resolve(projectRoot) === path22.resolve(os5.homedir());
14194
+ const isHomeDir = path23.resolve(projectRoot) === path23.resolve(os6.homedir());
13108
14195
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
13109
14196
  if (isHomeDir) {
13110
14197
  console.warn(