opencode-codebase-index 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
6885
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;
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,
@@ -8033,7 +8948,7 @@ var Indexer = class {
8033
8948
  const semanticResults = store.search(embedding, maxResults * 4);
8034
8949
  const vectorMs = performance2.now() - vectorStartTime;
8035
8950
  const keywordStartTime = performance2.now();
8036
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8951
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
8037
8952
  const keywordMs = performance2.now() - keywordStartTime;
8038
8953
  let branchChunkIds = null;
8039
8954
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -8070,7 +8985,7 @@ var Indexer = class {
8070
8985
  rrfK,
8071
8986
  rerankTopN,
8072
8987
  limit: maxResults,
8073
- hybridWeight,
8988
+ hybridWeight: effectiveHybridWeight,
8074
8989
  prioritizeSourcePaths: sourceIntent
8075
8990
  });
8076
8991
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -8170,8 +9085,7 @@ var Indexer = class {
8170
9085
  })
8171
9086
  );
8172
9087
  }
8173
- async keywordSearch(query, limit) {
8174
- const { store, invertedIndex } = await this.ensureInitialized();
9088
+ async keywordSearch(query, limit, store, invertedIndex) {
8175
9089
  const scores = invertedIndex.search(query);
8176
9090
  if (scores.size === 0) {
8177
9091
  return [];
@@ -8189,24 +9103,54 @@ var Indexer = class {
8189
9103
  return results.slice(0, limit);
8190
9104
  }
8191
9105
  async getStatus() {
8192
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9106
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
8193
9107
  const failedBatchesCount = this.getFailedBatchesCount();
9108
+ const vectorCount = store.count();
9109
+ const statusReadIssues = [...readIssues];
9110
+ let startupWarning = "";
9111
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9112
+ try {
9113
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9114
+ } catch (error) {
9115
+ const message = this.getDatabaseReadIssueMessage();
9116
+ statusReadIssues.push(this.createReadIssue("database", message));
9117
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9118
+ this.recordReadIssue("database", message, error);
9119
+ }
9120
+ }
9121
+ }
9122
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9123
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9124
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
8194
9125
  return {
8195
- indexed: store.count() > 0,
8196
- vectorCount: store.count(),
9126
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9127
+ vectorCount,
8197
9128
  provider: configuredProviderInfo.provider,
8198
9129
  model: configuredProviderInfo.modelInfo.model,
8199
9130
  indexPath: this.indexPath,
8200
9131
  currentBranch: this.currentBranch,
8201
9132
  baseBranch: this.baseBranch,
8202
- compatibility: this.indexCompatibility,
9133
+ compatibility,
8203
9134
  failedBatchesCount,
8204
9135
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8205
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9136
+ warning: warning || void 0
8206
9137
  };
8207
9138
  }
9139
+ async forceIndex(onProgress) {
9140
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9141
+ await this.ensureInitializedUnlocked(recoveredOwners);
9142
+ await this.clearIndexUnlocked();
9143
+ return this.indexUnlocked(onProgress, [], true);
9144
+ });
9145
+ }
8208
9146
  async clearIndex() {
8209
- const { store, invertedIndex, database } = await this.ensureInitialized();
9147
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9148
+ await this.ensureInitializedUnlocked(recoveredOwners);
9149
+ await this.clearIndexUnlocked();
9150
+ });
9151
+ }
9152
+ async clearIndexUnlocked() {
9153
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8210
9154
  if (this.config.scope === "global") {
8211
9155
  store.load();
8212
9156
  invertedIndex.load();
@@ -8233,7 +9177,7 @@ var Indexer = class {
8233
9177
  store.clear();
8234
9178
  store.save();
8235
9179
  invertedIndex.clear();
8236
- invertedIndex.save();
9180
+ this.saveInvertedIndex(invertedIndex);
8237
9181
  this.fileHashCache.clear();
8238
9182
  this.saveFileHashCache();
8239
9183
  database.clearAllIndexedData();
@@ -8257,14 +9201,7 @@ var Indexer = class {
8257
9201
  this.indexCompatibility = compatibility;
8258
9202
  return;
8259
9203
  }
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) {
9204
+ if (!this.isLocalProjectIndexPath()) {
8268
9205
  throw new Error(
8269
9206
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8270
9207
  );
@@ -8272,7 +9209,7 @@ var Indexer = class {
8272
9209
  store.clear();
8273
9210
  store.save();
8274
9211
  invertedIndex.clear();
8275
- invertedIndex.save();
9212
+ this.saveInvertedIndex(invertedIndex);
8276
9213
  this.fileHashCache.clear();
8277
9214
  this.saveFileHashCache();
8278
9215
  database.clearAllIndexedData();
@@ -8290,7 +9227,13 @@ var Indexer = class {
8290
9227
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8291
9228
  }
8292
9229
  async healthCheck() {
8293
- const { store, invertedIndex, database } = await this.ensureInitialized();
9230
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9231
+ await this.ensureInitializedUnlocked(recoveredOwners);
9232
+ return this.healthCheckUnlocked();
9233
+ });
9234
+ }
9235
+ async healthCheckUnlocked() {
9236
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8294
9237
  this.logger.gc("info", "Starting health check");
8295
9238
  const allMetadata = store.getAllMetadata();
8296
9239
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8303,7 +9246,7 @@ var Indexer = class {
8303
9246
  const removedChunkKeys = [];
8304
9247
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8305
9248
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8306
- if (!existsSync7(filePath)) {
9249
+ if (!existsSync8(filePath)) {
8307
9250
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8308
9251
  for (const key of chunkKeys) {
8309
9252
  removedChunkKeys.push(key);
@@ -8328,7 +9271,7 @@ var Indexer = class {
8328
9271
  const removedCount = removedChunkKeys.length;
8329
9272
  if (removedCount > 0) {
8330
9273
  store.save();
8331
- invertedIndex.save();
9274
+ this.saveInvertedIndex(invertedIndex);
8332
9275
  }
8333
9276
  let gcOrphanEmbeddings;
8334
9277
  let gcOrphanChunks;
@@ -8343,7 +9286,7 @@ var Indexer = class {
8343
9286
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8344
9287
  throw error;
8345
9288
  }
8346
- await this.ensureInitialized();
9289
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8347
9290
  return {
8348
9291
  removed: 0,
8349
9292
  filePaths: [],
@@ -8352,7 +9295,7 @@ var Indexer = class {
8352
9295
  gcOrphanSymbols: 0,
8353
9296
  gcOrphanCallEdges: 0,
8354
9297
  resetCorruptedIndex: true,
8355
- warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
9298
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8356
9299
  };
8357
9300
  }
8358
9301
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8365,7 +9308,13 @@ var Indexer = class {
8365
9308
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8366
9309
  }
8367
9310
  async retryFailedBatches() {
8368
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9311
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9312
+ await this.ensureInitializedUnlocked(recoveredOwners);
9313
+ return this.retryFailedBatchesUnlocked();
9314
+ });
9315
+ }
9316
+ async retryFailedBatchesUnlocked() {
9317
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8369
9318
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8370
9319
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8371
9320
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8529,7 +9478,7 @@ var Indexer = class {
8529
9478
  }
8530
9479
  if (succeeded > 0) {
8531
9480
  store.save();
8532
- invertedIndex.save();
9481
+ this.saveInvertedIndex(invertedIndex);
8533
9482
  }
8534
9483
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8535
9484
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8557,15 +9506,16 @@ var Indexer = class {
8557
9506
  }
8558
9507
  }
8559
9508
  async getDatabaseStats() {
8560
- const { database } = await this.ensureInitialized();
9509
+ const { database, readIssues } = await this.ensureInitialized();
9510
+ this.requireReadableComponents(readIssues, "database");
8561
9511
  return database.getStats();
8562
9512
  }
8563
9513
  getLogger() {
8564
9514
  return this.logger;
8565
9515
  }
8566
9516
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8567
- const { store, provider, database } = await this.ensureInitialized();
8568
- const compatibility = this.checkCompatibility();
9517
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9518
+ this.requireReadableComponents(readIssues, "vectors", "database");
8569
9519
  if (!compatibility.compatible) {
8570
9520
  throw new Error(
8571
9521
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8679,7 +9629,8 @@ var Indexer = class {
8679
9629
  );
8680
9630
  }
8681
9631
  async getCallers(targetName, callTypeFilter) {
8682
- const { database } = await this.ensureInitialized();
9632
+ const { database, readIssues } = await this.ensureInitialized();
9633
+ this.requireReadableComponents(readIssues, "database");
8683
9634
  const seen = /* @__PURE__ */ new Set();
8684
9635
  const results = [];
8685
9636
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8693,7 +9644,8 @@ var Indexer = class {
8693
9644
  return results;
8694
9645
  }
8695
9646
  async getCallees(symbolId, callTypeFilter) {
8696
- const { database } = await this.ensureInitialized();
9647
+ const { database, readIssues } = await this.ensureInitialized();
9648
+ this.requireReadableComponents(readIssues, "database");
8697
9649
  const seen = /* @__PURE__ */ new Set();
8698
9650
  const results = [];
8699
9651
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8707,43 +9659,50 @@ var Indexer = class {
8707
9659
  return results;
8708
9660
  }
8709
9661
  async findCallPath(fromName, toName, maxDepth) {
8710
- const { database } = await this.ensureInitialized();
9662
+ const { database, readIssues } = await this.ensureInitialized();
9663
+ this.requireReadableComponents(readIssues, "database");
8711
9664
  let shortest = [];
8712
9665
  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;
9666
+ const path24 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9667
+ if (path24.length > 0 && (shortest.length === 0 || path24.length < shortest.length)) {
9668
+ shortest = path24;
8716
9669
  }
8717
9670
  }
8718
9671
  return shortest;
8719
9672
  }
8720
9673
  async getSymbolsForBranch(branch) {
8721
- const { database } = await this.ensureInitialized();
9674
+ const { database, readIssues } = await this.ensureInitialized();
9675
+ this.requireReadableComponents(readIssues, "database");
8722
9676
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8723
9677
  return database.getSymbolsForBranch(resolvedBranch);
8724
9678
  }
8725
9679
  async getSymbolsForFiles(filePaths, branch) {
8726
- const { database } = await this.ensureInitialized();
9680
+ const { database, readIssues } = await this.ensureInitialized();
9681
+ this.requireReadableComponents(readIssues, "database");
8727
9682
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8728
9683
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8729
9684
  }
8730
9685
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8731
- const { database } = await this.ensureInitialized();
9686
+ const { database, readIssues } = await this.ensureInitialized();
9687
+ this.requireReadableComponents(readIssues, "database");
8732
9688
  const branch = this.getBranchCatalogKey();
8733
9689
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8734
9690
  }
8735
9691
  async detectCommunities(branch, symbolIds) {
8736
- const { database } = await this.ensureInitialized();
9692
+ const { database, readIssues } = await this.ensureInitialized();
9693
+ this.requireReadableComponents(readIssues, "database");
8737
9694
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8738
9695
  return database.detectCommunities(resolvedBranch, symbolIds);
8739
9696
  }
8740
9697
  async computeCentrality(branch) {
8741
- const { database } = await this.ensureInitialized();
9698
+ const { database, readIssues } = await this.ensureInitialized();
9699
+ this.requireReadableComponents(readIssues, "database");
8742
9700
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8743
9701
  return database.computeCentrality(resolvedBranch);
8744
9702
  }
8745
9703
  async getPrImpact(opts) {
8746
- const { database } = await this.ensureInitialized();
9704
+ const { database, readIssues } = await this.ensureInitialized();
9705
+ this.requireReadableComponents(readIssues, "database");
8747
9706
  const execFileAsync3 = promisify3(execFile3);
8748
9707
  const changedFilesResult = await getChangedFiles({
8749
9708
  pr: opts.pr,
@@ -8766,7 +9725,7 @@ var Indexer = class {
8766
9725
  "Run index_codebase first to build the call graph and symbol index for this branch."
8767
9726
  );
8768
9727
  }
8769
- const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
9728
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8770
9729
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8771
9730
  const directIds = directSymbols.map((s) => s.id);
8772
9731
  const direction = opts.direction ?? "both";
@@ -8849,7 +9808,7 @@ var Indexer = class {
8849
9808
  projectRoot: this.projectRoot,
8850
9809
  baseBranch: this.baseBranch
8851
9810
  });
8852
- const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
9811
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8853
9812
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8854
9813
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8855
9814
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8899,7 +9858,8 @@ var Indexer = class {
8899
9858
  };
8900
9859
  }
8901
9860
  async getVisualizationData(options) {
8902
- const { database, store } = await this.ensureInitialized();
9861
+ const { database, store, readIssues } = await this.ensureInitialized();
9862
+ this.requireReadableComponents(readIssues, "vectors", "database");
8903
9863
  const seenSymbols = /* @__PURE__ */ new Map();
8904
9864
  const seenEdges = /* @__PURE__ */ new Map();
8905
9865
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8912,12 +9872,12 @@ var Indexer = class {
8912
9872
  if (meta.filePath) filePaths.add(meta.filePath);
8913
9873
  }
8914
9874
  const directory = options?.directory?.replace(/\/$/, "");
8915
- const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
9875
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8916
9876
  for (const filePath of filePaths) {
8917
9877
  if (directory) {
8918
- const absoluteFilePath = path12.resolve(filePath);
9878
+ const absoluteFilePath = path13.resolve(filePath);
8919
9879
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8920
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
9880
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8921
9881
  if (!matchesRelative && !matchesProjectRelative) {
8922
9882
  continue;
8923
9883
  }
@@ -8939,53 +9899,64 @@ var Indexer = class {
8939
9899
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8940
9900
  }
8941
9901
  async close() {
8942
- await this.database?.close();
9902
+ this.database?.close();
9903
+ for (const database of this.retiredDatabases) {
9904
+ database.close();
9905
+ }
9906
+ this.retiredDatabases = [];
8943
9907
  this.database = null;
8944
9908
  this.store = null;
8945
9909
  this.invertedIndex = null;
8946
9910
  this.provider = null;
8947
9911
  this.reranker = null;
9912
+ this.configuredProviderInfo = null;
9913
+ this.indexCompatibility = null;
9914
+ this.initializationMode = "none";
9915
+ this.readIssues = [];
9916
+ this.readerArtifactFingerprint = null;
9917
+ this.writerArtifactFingerprint = null;
9918
+ this.readerArtifactRetryAfter.clear();
8948
9919
  }
8949
9920
  };
8950
9921
 
8951
9922
  // src/tools/knowledge-base-paths.ts
8952
- import * as path13 from "path";
9923
+ import * as path14 from "path";
8953
9924
  function resolveConfigPathValue(value, baseDir) {
8954
9925
  const trimmed = value.trim();
8955
9926
  if (!trimmed) {
8956
9927
  return trimmed;
8957
9928
  }
8958
- const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
8959
- return path13.normalize(absolutePath);
9929
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9930
+ return path14.normalize(absolutePath);
8960
9931
  }
8961
9932
  function serializeConfigPathValue(value, baseDir) {
8962
9933
  const trimmed = value.trim();
8963
9934
  if (!trimmed) {
8964
9935
  return trimmed;
8965
9936
  }
8966
- if (!path13.isAbsolute(trimmed)) {
8967
- return normalizePathSeparators(path13.normalize(trimmed));
9937
+ if (!path14.isAbsolute(trimmed)) {
9938
+ return normalizePathSeparators(path14.normalize(trimmed));
8968
9939
  }
8969
- const relativePath = path13.relative(baseDir, trimmed);
8970
- if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
8971
- return normalizePathSeparators(path13.normalize(relativePath || "."));
9940
+ const relativePath = path14.relative(baseDir, trimmed);
9941
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9942
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8972
9943
  }
8973
- return path13.normalize(trimmed);
9944
+ return path14.normalize(trimmed);
8974
9945
  }
8975
9946
  function resolveKnowledgeBasePath(value, projectRoot) {
8976
- return path13.isAbsolute(value) ? value : path13.resolve(projectRoot, value);
9947
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
8977
9948
  }
8978
9949
  function normalizeKnowledgeBasePath2(value, projectRoot) {
8979
- return path13.normalize(resolveKnowledgeBasePath(value, projectRoot));
9950
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
8980
9951
  }
8981
9952
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
8982
- const normalizedInput = path13.normalize(inputPath);
9953
+ const normalizedInput = path14.normalize(inputPath);
8983
9954
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
8984
9955
  }
8985
9956
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
8986
- const normalizedInput = path13.normalize(inputPath);
9957
+ const normalizedInput = path14.normalize(inputPath);
8987
9958
  return knowledgeBases.findIndex(
8988
- (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
9959
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
8989
9960
  );
8990
9961
  }
8991
9962
 
@@ -9085,6 +10056,10 @@ function formatStatus(status) {
9085
10056
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
9086
10057
  }
9087
10058
  }
10059
+ if (status.warning) {
10060
+ lines.push("");
10061
+ lines.push(`INDEX WARNING: ${status.warning}`);
10062
+ }
9088
10063
  if (status.compatibility && !status.compatibility.compatible) {
9089
10064
  lines.push("");
9090
10065
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -9190,16 +10165,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
9190
10165
  return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
9191
10166
  }).join("\n");
9192
10167
  }
9193
- function formatCallGraphPath(from, to, path23) {
9194
- if (path23.length === 0) {
10168
+ function formatCallGraphPath(from, to, path24) {
10169
+ if (path24.length === 0) {
9195
10170
  return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
9196
10171
  }
9197
- const formatted = path23.map((hop, index) => {
10172
+ const formatted = path24.map((hop, index) => {
9198
10173
  const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
9199
10174
  const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
9200
10175
  return `${prefix} ${hop.symbolName}${location}`;
9201
10176
  });
9202
- return `Path (${path23.length} hops):
10177
+ return `Path (${path24.length} hops):
9203
10178
  ${formatted.join("\n")}`;
9204
10179
  }
9205
10180
  function formatResultHeader(result, index) {
@@ -9239,8 +10214,8 @@ ${truncateContent(r.content)}
9239
10214
  }
9240
10215
 
9241
10216
  // src/tools/config-state.ts
9242
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
9243
- import * as path14 from "path";
10217
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10218
+ import * as path15 from "path";
9244
10219
  function normalizeKnowledgeBasePaths(config, projectRoot) {
9245
10220
  const normalized = { ...config };
9246
10221
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -9267,10 +10242,10 @@ function loadEditableConfig(projectRoot, host = "opencode") {
9267
10242
  }
9268
10243
  function saveConfig(projectRoot, config, host = "opencode") {
9269
10244
  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 });
10245
+ const configDir = path15.dirname(configPath);
10246
+ const configBaseDir = path15.dirname(configDir);
10247
+ if (!existsSync9(configDir)) {
10248
+ mkdirSync4(configDir, { recursive: true });
9274
10249
  }
9275
10250
  const serializableConfig = { ...config };
9276
10251
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9278,13 +10253,31 @@ function saveConfig(projectRoot, config, host = "opencode") {
9278
10253
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9279
10254
  );
9280
10255
  }
9281
- writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10256
+ writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9282
10257
  }
9283
10258
 
9284
10259
  // src/tools/operations.ts
9285
10260
  var indexerCache = /* @__PURE__ */ new Map();
9286
10261
  var configCache = /* @__PURE__ */ new Map();
9287
10262
  var defaultProjectRoots = /* @__PURE__ */ new Map();
10263
+ function getIndexBusyResult(error) {
10264
+ if (!isIndexLockContentionError(error)) return null;
10265
+ const owner = error.owner;
10266
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10267
+ if (error.reason === "legacy-lock") {
10268
+ return {
10269
+ kind: "busy",
10270
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10271
+ };
10272
+ }
10273
+ if (error.reason === "unknown-owner") {
10274
+ return {
10275
+ kind: "busy",
10276
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10277
+ };
10278
+ }
10279
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10280
+ }
9288
10281
  function getProjectRoot(projectRoot, host) {
9289
10282
  if (projectRoot) {
9290
10283
  return projectRoot;
@@ -9329,8 +10322,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
9329
10322
  }
9330
10323
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
9331
10324
  const root = getProjectRoot(projectRoot, host);
9332
- const localIndexPath = path15.join(root, getHostProjectIndexRelativePath(host));
9333
- if (existsSync9(localIndexPath)) {
10325
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10326
+ if (existsSync10(localIndexPath)) {
9334
10327
  return false;
9335
10328
  }
9336
10329
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
@@ -9386,35 +10379,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
9386
10379
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
9387
10380
  const root = getProjectRoot(projectRoot, host);
9388
10381
  const key = getIndexerCacheKey(root, host);
9389
- const cachedConfig = configCache.get(key);
9390
10382
  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)
10383
+ const runtimeConfig = configCache.get(key);
10384
+ try {
10385
+ if (args.estimateOnly) {
10386
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10387
+ }
10388
+ const runIndex = async (target) => {
10389
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10390
+ return operation((progress) => {
10391
+ if (onProgress) {
10392
+ return onProgress(formatProgressTitle(progress), {
10393
+ phase: progress.phase,
10394
+ filesProcessed: progress.filesProcessed,
10395
+ totalFiles: progress.totalFiles,
10396
+ chunksProcessed: progress.chunksProcessed,
10397
+ totalChunks: progress.totalChunks,
10398
+ percentage: calculatePercentage(progress)
10399
+ });
10400
+ }
10401
+ return Promise.resolve();
9413
10402
  });
10403
+ };
10404
+ let stats;
10405
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10406
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10407
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10408
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10409
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10410
+ indexer = getIndexerForProject(root, host);
10411
+ return runIndex(indexer);
10412
+ }, { completeRecoveries: false });
10413
+ } else {
10414
+ stats = await runIndex(indexer);
9414
10415
  }
9415
- return Promise.resolve();
9416
- });
9417
- return { kind: "stats", stats };
10416
+ return { kind: "stats", stats };
10417
+ } catch (error) {
10418
+ const busyResult = getIndexBusyResult(error);
10419
+ if (!busyResult) throw error;
10420
+ return busyResult;
10421
+ }
9418
10422
  }
9419
10423
  async function getIndexStatus(projectRoot, host) {
9420
10424
  const indexer = getIndexerForProject(projectRoot, host);
@@ -9424,6 +10428,15 @@ async function getIndexHealthCheck(projectRoot, host) {
9424
10428
  const indexer = getIndexerForProject(projectRoot, host);
9425
10429
  return indexer.healthCheck();
9426
10430
  }
10431
+ async function runIndexHealthCheck(projectRoot, host) {
10432
+ try {
10433
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
10434
+ } catch (error) {
10435
+ const busyResult = getIndexBusyResult(error);
10436
+ if (!busyResult) throw error;
10437
+ return busyResult;
10438
+ }
10439
+ }
9427
10440
  async function getIndexMetrics(projectRoot, host) {
9428
10441
  const indexer = getIndexerForProject(projectRoot, host);
9429
10442
  const logger = indexer.getLogger();
@@ -9479,15 +10492,15 @@ async function getIndexLogs(projectRoot, host, args) {
9479
10492
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9480
10493
  const root = getProjectRoot(projectRoot, host);
9481
10494
  const inputPath = knowledgeBasePath.trim();
9482
- const normalizedPath = path15.resolve(
9483
- path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10495
+ const normalizedPath = path16.resolve(
10496
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9484
10497
  );
9485
- if (!existsSync9(normalizedPath)) {
10498
+ if (!existsSync10(normalizedPath)) {
9486
10499
  return `Error: Directory does not exist: ${normalizedPath}`;
9487
10500
  }
9488
10501
  let realPath;
9489
10502
  try {
9490
- realPath = realpathSync(normalizedPath);
10503
+ realPath = realpathSync2(normalizedPath);
9491
10504
  } catch {
9492
10505
  return `Error: Cannot resolve path: ${normalizedPath}`;
9493
10506
  }
@@ -9516,13 +10529,13 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9516
10529
  }
9517
10530
  }
9518
10531
  for (const dotDir of sensitiveDotDirs) {
9519
- const sensitiveDir = path15.join(homeDir, dotDir);
10532
+ const sensitiveDir = path16.join(homeDir, dotDir);
9520
10533
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9521
10534
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9522
10535
  }
9523
10536
  }
9524
10537
  try {
9525
- const stat4 = statSync3(normalizedPath);
10538
+ const stat4 = statSync4(normalizedPath);
9526
10539
  if (!stat4.isDirectory()) {
9527
10540
  return `Error: Path is not a directory: ${normalizedPath}`;
9528
10541
  }
@@ -9562,7 +10575,7 @@ function listKnowledgeBases(projectRoot, host) {
9562
10575
  for (let i = 0; i < knowledgeBases.length; i++) {
9563
10576
  const kb = knowledgeBases[i];
9564
10577
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9565
- const exists = existsSync9(resolvedPath);
10578
+ const exists = existsSync10(resolvedPath);
9566
10579
  result += `[${i + 1}] ${kb}
9567
10580
  `;
9568
10581
  result += ` Resolved: ${resolvedPath}
@@ -9571,7 +10584,7 @@ function listKnowledgeBases(projectRoot, host) {
9571
10584
  `;
9572
10585
  if (exists) {
9573
10586
  try {
9574
- const stat4 = statSync3(resolvedPath);
10587
+ const stat4 = statSync4(resolvedPath);
9575
10588
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
9576
10589
  `;
9577
10590
  } catch {
@@ -9579,7 +10592,7 @@ function listKnowledgeBases(projectRoot, host) {
9579
10592
  }
9580
10593
  result += "\n";
9581
10594
  }
9582
- const hasHostConfig = existsSync9(path15.join(root, getHostProjectConfigRelativePath(host)));
10595
+ const hasHostConfig = existsSync10(path16.join(root, getHostProjectConfigRelativePath(host)));
9583
10596
  if (hasHostConfig) {
9584
10597
  result += `
9585
10598
  Config sources: 1 file(s).`;
@@ -9702,7 +10715,7 @@ var ReaddirpStream = class extends Readable {
9702
10715
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9703
10716
  const statMethod = opts.lstat ? lstat : stat;
9704
10717
  if (wantBigintFsStats) {
9705
- this._stat = (path23) => statMethod(path23, { bigint: true });
10718
+ this._stat = (path24) => statMethod(path24, { bigint: true });
9706
10719
  } else {
9707
10720
  this._stat = statMethod;
9708
10721
  }
@@ -9727,8 +10740,8 @@ var ReaddirpStream = class extends Readable {
9727
10740
  const par = this.parent;
9728
10741
  const fil = par && par.files;
9729
10742
  if (fil && fil.length > 0) {
9730
- const { path: path23, depth } = par;
9731
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
10743
+ const { path: path24, depth } = par;
10744
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
9732
10745
  const awaited = await Promise.all(slice);
9733
10746
  for (const entry of awaited) {
9734
10747
  if (!entry)
@@ -9768,20 +10781,20 @@ var ReaddirpStream = class extends Readable {
9768
10781
  this.reading = false;
9769
10782
  }
9770
10783
  }
9771
- async _exploreDir(path23, depth) {
10784
+ async _exploreDir(path24, depth) {
9772
10785
  let files;
9773
10786
  try {
9774
- files = await readdir(path23, this._rdOptions);
10787
+ files = await readdir(path24, this._rdOptions);
9775
10788
  } catch (error) {
9776
10789
  this._onError(error);
9777
10790
  }
9778
- return { files, depth, path: path23 };
10791
+ return { files, depth, path: path24 };
9779
10792
  }
9780
- async _formatEntry(dirent, path23) {
10793
+ async _formatEntry(dirent, path24) {
9781
10794
  let entry;
9782
10795
  const basename5 = this._isDirent ? dirent.name : dirent;
9783
10796
  try {
9784
- const fullPath = presolve(pjoin(path23, basename5));
10797
+ const fullPath = presolve(pjoin(path24, basename5));
9785
10798
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
9786
10799
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9787
10800
  } catch (err) {
@@ -10181,16 +11194,16 @@ var delFromSet = (main, prop, item) => {
10181
11194
  };
10182
11195
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
10183
11196
  var FsWatchInstances = /* @__PURE__ */ new Map();
10184
- function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
11197
+ function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
10185
11198
  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));
11199
+ listener(path24);
11200
+ emitRaw(rawEvent, evPath, { watchedPath: path24 });
11201
+ if (evPath && path24 !== evPath) {
11202
+ fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
10190
11203
  }
10191
11204
  };
10192
11205
  try {
10193
- return fs_watch(path23, {
11206
+ return fs_watch(path24, {
10194
11207
  persistent: options.persistent
10195
11208
  }, handleEvent);
10196
11209
  } catch (error) {
@@ -10206,12 +11219,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
10206
11219
  listener(val1, val2, val3);
10207
11220
  });
10208
11221
  };
10209
- var setFsWatchListener = (path23, fullPath, options, handlers) => {
11222
+ var setFsWatchListener = (path24, fullPath, options, handlers) => {
10210
11223
  const { listener, errHandler, rawEmitter } = handlers;
10211
11224
  let cont = FsWatchInstances.get(fullPath);
10212
11225
  let watcher;
10213
11226
  if (!options.persistent) {
10214
- watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
11227
+ watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
10215
11228
  if (!watcher)
10216
11229
  return;
10217
11230
  return watcher.close.bind(watcher);
@@ -10222,7 +11235,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10222
11235
  addAndConvert(cont, KEY_RAW, rawEmitter);
10223
11236
  } else {
10224
11237
  watcher = createFsWatchInstance(
10225
- path23,
11238
+ path24,
10226
11239
  options,
10227
11240
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10228
11241
  errHandler,
@@ -10237,7 +11250,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10237
11250
  cont.watcherUnusable = true;
10238
11251
  if (isWindows && error.code === "EPERM") {
10239
11252
  try {
10240
- const fd = await open(path23, "r");
11253
+ const fd = await open(path24, "r");
10241
11254
  await fd.close();
10242
11255
  broadcastErr(error);
10243
11256
  } catch (err) {
@@ -10268,7 +11281,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
10268
11281
  };
10269
11282
  };
10270
11283
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10271
- var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
11284
+ var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
10272
11285
  const { listener, rawEmitter } = handlers;
10273
11286
  let cont = FsWatchFileInstances.get(fullPath);
10274
11287
  const copts = cont && cont.options;
@@ -10290,7 +11303,7 @@ var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
10290
11303
  });
10291
11304
  const currmtime = curr.mtimeMs;
10292
11305
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10293
- foreach(cont.listeners, (listener2) => listener2(path23, curr));
11306
+ foreach(cont.listeners, (listener2) => listener2(path24, curr));
10294
11307
  }
10295
11308
  })
10296
11309
  };
@@ -10320,13 +11333,13 @@ var NodeFsHandler = class {
10320
11333
  * @param listener on fs change
10321
11334
  * @returns closer for the watcher instance
10322
11335
  */
10323
- _watchWithNodeFs(path23, listener) {
11336
+ _watchWithNodeFs(path24, listener) {
10324
11337
  const opts = this.fsw.options;
10325
- const directory = sp.dirname(path23);
10326
- const basename5 = sp.basename(path23);
11338
+ const directory = sp.dirname(path24);
11339
+ const basename5 = sp.basename(path24);
10327
11340
  const parent = this.fsw._getWatchedDir(directory);
10328
11341
  parent.add(basename5);
10329
- const absolutePath = sp.resolve(path23);
11342
+ const absolutePath = sp.resolve(path24);
10330
11343
  const options = {
10331
11344
  persistent: opts.persistent
10332
11345
  };
@@ -10336,12 +11349,12 @@ var NodeFsHandler = class {
10336
11349
  if (opts.usePolling) {
10337
11350
  const enableBin = opts.interval !== opts.binaryInterval;
10338
11351
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
10339
- closer = setFsWatchFileListener(path23, absolutePath, options, {
11352
+ closer = setFsWatchFileListener(path24, absolutePath, options, {
10340
11353
  listener,
10341
11354
  rawEmitter: this.fsw._emitRaw
10342
11355
  });
10343
11356
  } else {
10344
- closer = setFsWatchListener(path23, absolutePath, options, {
11357
+ closer = setFsWatchListener(path24, absolutePath, options, {
10345
11358
  listener,
10346
11359
  errHandler: this._boundHandleError,
10347
11360
  rawEmitter: this.fsw._emitRaw
@@ -10363,7 +11376,7 @@ var NodeFsHandler = class {
10363
11376
  let prevStats = stats;
10364
11377
  if (parent.has(basename5))
10365
11378
  return;
10366
- const listener = async (path23, newStats) => {
11379
+ const listener = async (path24, newStats) => {
10367
11380
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10368
11381
  return;
10369
11382
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10377,11 +11390,11 @@ var NodeFsHandler = class {
10377
11390
  this.fsw._emit(EV.CHANGE, file, newStats2);
10378
11391
  }
10379
11392
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10380
- this.fsw._closeFile(path23);
11393
+ this.fsw._closeFile(path24);
10381
11394
  prevStats = newStats2;
10382
11395
  const closer2 = this._watchWithNodeFs(file, listener);
10383
11396
  if (closer2)
10384
- this.fsw._addPathCloser(path23, closer2);
11397
+ this.fsw._addPathCloser(path24, closer2);
10385
11398
  } else {
10386
11399
  prevStats = newStats2;
10387
11400
  }
@@ -10413,7 +11426,7 @@ var NodeFsHandler = class {
10413
11426
  * @param item basename of this item
10414
11427
  * @returns true if no more processing is needed for this entry.
10415
11428
  */
10416
- async _handleSymlink(entry, directory, path23, item) {
11429
+ async _handleSymlink(entry, directory, path24, item) {
10417
11430
  if (this.fsw.closed) {
10418
11431
  return;
10419
11432
  }
@@ -10423,7 +11436,7 @@ var NodeFsHandler = class {
10423
11436
  this.fsw._incrReadyCount();
10424
11437
  let linkPath;
10425
11438
  try {
10426
- linkPath = await fsrealpath(path23);
11439
+ linkPath = await fsrealpath(path24);
10427
11440
  } catch (e) {
10428
11441
  this.fsw._emitReady();
10429
11442
  return true;
@@ -10433,12 +11446,12 @@ var NodeFsHandler = class {
10433
11446
  if (dir.has(item)) {
10434
11447
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10435
11448
  this.fsw._symlinkPaths.set(full, linkPath);
10436
- this.fsw._emit(EV.CHANGE, path23, entry.stats);
11449
+ this.fsw._emit(EV.CHANGE, path24, entry.stats);
10437
11450
  }
10438
11451
  } else {
10439
11452
  dir.add(item);
10440
11453
  this.fsw._symlinkPaths.set(full, linkPath);
10441
- this.fsw._emit(EV.ADD, path23, entry.stats);
11454
+ this.fsw._emit(EV.ADD, path24, entry.stats);
10442
11455
  }
10443
11456
  this.fsw._emitReady();
10444
11457
  return true;
@@ -10468,9 +11481,9 @@ var NodeFsHandler = class {
10468
11481
  return;
10469
11482
  }
10470
11483
  const item = entry.path;
10471
- let path23 = sp.join(directory, item);
11484
+ let path24 = sp.join(directory, item);
10472
11485
  current.add(item);
10473
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
11486
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
10474
11487
  return;
10475
11488
  }
10476
11489
  if (this.fsw.closed) {
@@ -10479,8 +11492,8 @@ var NodeFsHandler = class {
10479
11492
  }
10480
11493
  if (item === target || !target && !previous.has(item)) {
10481
11494
  this.fsw._incrReadyCount();
10482
- path23 = sp.join(dir, sp.relative(dir, path23));
10483
- this._addToNodeFs(path23, initialAdd, wh, depth + 1);
11495
+ path24 = sp.join(dir, sp.relative(dir, path24));
11496
+ this._addToNodeFs(path24, initialAdd, wh, depth + 1);
10484
11497
  }
10485
11498
  }).on(EV.ERROR, this._boundHandleError);
10486
11499
  return new Promise((resolve13, reject) => {
@@ -10549,13 +11562,13 @@ var NodeFsHandler = class {
10549
11562
  * @param depth Child path actually targeted for watch
10550
11563
  * @param target Child path actually targeted for watch
10551
11564
  */
10552
- async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
11565
+ async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
10553
11566
  const ready = this.fsw._emitReady;
10554
- if (this.fsw._isIgnored(path23) || this.fsw.closed) {
11567
+ if (this.fsw._isIgnored(path24) || this.fsw.closed) {
10555
11568
  ready();
10556
11569
  return false;
10557
11570
  }
10558
- const wh = this.fsw._getWatchHelpers(path23);
11571
+ const wh = this.fsw._getWatchHelpers(path24);
10559
11572
  if (priorWh) {
10560
11573
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10561
11574
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10571,8 +11584,8 @@ var NodeFsHandler = class {
10571
11584
  const follow = this.fsw.options.followSymlinks;
10572
11585
  let closer;
10573
11586
  if (stats.isDirectory()) {
10574
- const absPath = sp.resolve(path23);
10575
- const targetPath = follow ? await fsrealpath(path23) : path23;
11587
+ const absPath = sp.resolve(path24);
11588
+ const targetPath = follow ? await fsrealpath(path24) : path24;
10576
11589
  if (this.fsw.closed)
10577
11590
  return;
10578
11591
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10582,29 +11595,29 @@ var NodeFsHandler = class {
10582
11595
  this.fsw._symlinkPaths.set(absPath, targetPath);
10583
11596
  }
10584
11597
  } else if (stats.isSymbolicLink()) {
10585
- const targetPath = follow ? await fsrealpath(path23) : path23;
11598
+ const targetPath = follow ? await fsrealpath(path24) : path24;
10586
11599
  if (this.fsw.closed)
10587
11600
  return;
10588
11601
  const parent = sp.dirname(wh.watchPath);
10589
11602
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10590
11603
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10591
- closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
11604
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
10592
11605
  if (this.fsw.closed)
10593
11606
  return;
10594
11607
  if (targetPath !== void 0) {
10595
- this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
11608
+ this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
10596
11609
  }
10597
11610
  } else {
10598
11611
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10599
11612
  }
10600
11613
  ready();
10601
11614
  if (closer)
10602
- this.fsw._addPathCloser(path23, closer);
11615
+ this.fsw._addPathCloser(path24, closer);
10603
11616
  return false;
10604
11617
  } catch (error) {
10605
11618
  if (this.fsw._handleError(error)) {
10606
11619
  ready();
10607
- return path23;
11620
+ return path24;
10608
11621
  }
10609
11622
  }
10610
11623
  }
@@ -10647,24 +11660,24 @@ function createPattern(matcher) {
10647
11660
  }
10648
11661
  return () => false;
10649
11662
  }
10650
- function normalizePath(path23) {
10651
- if (typeof path23 !== "string")
11663
+ function normalizePath(path24) {
11664
+ if (typeof path24 !== "string")
10652
11665
  throw new Error("string expected");
10653
- path23 = sp2.normalize(path23);
10654
- path23 = path23.replace(/\\/g, "/");
11666
+ path24 = sp2.normalize(path24);
11667
+ path24 = path24.replace(/\\/g, "/");
10655
11668
  let prepend = false;
10656
- if (path23.startsWith("//"))
11669
+ if (path24.startsWith("//"))
10657
11670
  prepend = true;
10658
- path23 = path23.replace(DOUBLE_SLASH_RE, "/");
11671
+ path24 = path24.replace(DOUBLE_SLASH_RE, "/");
10659
11672
  if (prepend)
10660
- path23 = "/" + path23;
10661
- return path23;
11673
+ path24 = "/" + path24;
11674
+ return path24;
10662
11675
  }
10663
11676
  function matchPatterns(patterns, testString, stats) {
10664
- const path23 = normalizePath(testString);
11677
+ const path24 = normalizePath(testString);
10665
11678
  for (let index = 0; index < patterns.length; index++) {
10666
11679
  const pattern = patterns[index];
10667
- if (pattern(path23, stats)) {
11680
+ if (pattern(path24, stats)) {
10668
11681
  return true;
10669
11682
  }
10670
11683
  }
@@ -10702,19 +11715,19 @@ var toUnix = (string) => {
10702
11715
  }
10703
11716
  return str;
10704
11717
  };
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));
11718
+ var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
11719
+ var normalizeIgnored = (cwd = "") => (path24) => {
11720
+ if (typeof path24 === "string") {
11721
+ return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
10709
11722
  } else {
10710
- return path23;
11723
+ return path24;
10711
11724
  }
10712
11725
  };
10713
- var getAbsolutePath = (path23, cwd) => {
10714
- if (sp2.isAbsolute(path23)) {
10715
- return path23;
11726
+ var getAbsolutePath = (path24, cwd) => {
11727
+ if (sp2.isAbsolute(path24)) {
11728
+ return path24;
10716
11729
  }
10717
- return sp2.join(cwd, path23);
11730
+ return sp2.join(cwd, path24);
10718
11731
  };
10719
11732
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10720
11733
  var DirEntry = class {
@@ -10779,10 +11792,10 @@ var WatchHelper = class {
10779
11792
  dirParts;
10780
11793
  followSymlinks;
10781
11794
  statMethod;
10782
- constructor(path23, follow, fsw) {
11795
+ constructor(path24, follow, fsw) {
10783
11796
  this.fsw = fsw;
10784
- const watchPath = path23;
10785
- this.path = path23 = path23.replace(REPLACER_RE, "");
11797
+ const watchPath = path24;
11798
+ this.path = path24 = path24.replace(REPLACER_RE, "");
10786
11799
  this.watchPath = watchPath;
10787
11800
  this.fullWatchPath = sp2.resolve(watchPath);
10788
11801
  this.dirParts = [];
@@ -10922,20 +11935,20 @@ var FSWatcher = class extends EventEmitter2 {
10922
11935
  this._closePromise = void 0;
10923
11936
  let paths = unifyPaths(paths_);
10924
11937
  if (cwd) {
10925
- paths = paths.map((path23) => {
10926
- const absPath = getAbsolutePath(path23, cwd);
11938
+ paths = paths.map((path24) => {
11939
+ const absPath = getAbsolutePath(path24, cwd);
10927
11940
  return absPath;
10928
11941
  });
10929
11942
  }
10930
- paths.forEach((path23) => {
10931
- this._removeIgnoredPath(path23);
11943
+ paths.forEach((path24) => {
11944
+ this._removeIgnoredPath(path24);
10932
11945
  });
10933
11946
  this._userIgnored = void 0;
10934
11947
  if (!this._readyCount)
10935
11948
  this._readyCount = 0;
10936
11949
  this._readyCount += paths.length;
10937
- Promise.all(paths.map(async (path23) => {
10938
- const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
11950
+ Promise.all(paths.map(async (path24) => {
11951
+ const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
10939
11952
  if (res)
10940
11953
  this._emitReady();
10941
11954
  return res;
@@ -10957,17 +11970,17 @@ var FSWatcher = class extends EventEmitter2 {
10957
11970
  return this;
10958
11971
  const paths = unifyPaths(paths_);
10959
11972
  const { cwd } = this.options;
10960
- paths.forEach((path23) => {
10961
- if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
11973
+ paths.forEach((path24) => {
11974
+ if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
10962
11975
  if (cwd)
10963
- path23 = sp2.join(cwd, path23);
10964
- path23 = sp2.resolve(path23);
11976
+ path24 = sp2.join(cwd, path24);
11977
+ path24 = sp2.resolve(path24);
10965
11978
  }
10966
- this._closePath(path23);
10967
- this._addIgnoredPath(path23);
10968
- if (this._watched.has(path23)) {
11979
+ this._closePath(path24);
11980
+ this._addIgnoredPath(path24);
11981
+ if (this._watched.has(path24)) {
10969
11982
  this._addIgnoredPath({
10970
- path: path23,
11983
+ path: path24,
10971
11984
  recursive: true
10972
11985
  });
10973
11986
  }
@@ -11031,38 +12044,38 @@ var FSWatcher = class extends EventEmitter2 {
11031
12044
  * @param stats arguments to be passed with event
11032
12045
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
11033
12046
  */
11034
- async _emit(event, path23, stats) {
12047
+ async _emit(event, path24, stats) {
11035
12048
  if (this.closed)
11036
12049
  return;
11037
12050
  const opts = this.options;
11038
12051
  if (isWindows)
11039
- path23 = sp2.normalize(path23);
12052
+ path24 = sp2.normalize(path24);
11040
12053
  if (opts.cwd)
11041
- path23 = sp2.relative(opts.cwd, path23);
11042
- const args = [path23];
12054
+ path24 = sp2.relative(opts.cwd, path24);
12055
+ const args = [path24];
11043
12056
  if (stats != null)
11044
12057
  args.push(stats);
11045
12058
  const awf = opts.awaitWriteFinish;
11046
12059
  let pw;
11047
- if (awf && (pw = this._pendingWrites.get(path23))) {
12060
+ if (awf && (pw = this._pendingWrites.get(path24))) {
11048
12061
  pw.lastChange = /* @__PURE__ */ new Date();
11049
12062
  return this;
11050
12063
  }
11051
12064
  if (opts.atomic) {
11052
12065
  if (event === EVENTS.UNLINK) {
11053
- this._pendingUnlinks.set(path23, [event, ...args]);
12066
+ this._pendingUnlinks.set(path24, [event, ...args]);
11054
12067
  setTimeout(() => {
11055
- this._pendingUnlinks.forEach((entry, path24) => {
12068
+ this._pendingUnlinks.forEach((entry, path25) => {
11056
12069
  this.emit(...entry);
11057
12070
  this.emit(EVENTS.ALL, ...entry);
11058
- this._pendingUnlinks.delete(path24);
12071
+ this._pendingUnlinks.delete(path25);
11059
12072
  });
11060
12073
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
11061
12074
  return this;
11062
12075
  }
11063
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
12076
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
11064
12077
  event = EVENTS.CHANGE;
11065
- this._pendingUnlinks.delete(path23);
12078
+ this._pendingUnlinks.delete(path24);
11066
12079
  }
11067
12080
  }
11068
12081
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -11080,16 +12093,16 @@ var FSWatcher = class extends EventEmitter2 {
11080
12093
  this.emitWithAll(event, args);
11081
12094
  }
11082
12095
  };
11083
- this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
12096
+ this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
11084
12097
  return this;
11085
12098
  }
11086
12099
  if (event === EVENTS.CHANGE) {
11087
- const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
12100
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
11088
12101
  if (isThrottled)
11089
12102
  return this;
11090
12103
  }
11091
12104
  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;
12105
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
11093
12106
  let stats2;
11094
12107
  try {
11095
12108
  stats2 = await stat3(fullPath);
@@ -11120,23 +12133,23 @@ var FSWatcher = class extends EventEmitter2 {
11120
12133
  * @param timeout duration of time to suppress duplicate actions
11121
12134
  * @returns tracking object or false if action should be suppressed
11122
12135
  */
11123
- _throttle(actionType, path23, timeout) {
12136
+ _throttle(actionType, path24, timeout) {
11124
12137
  if (!this._throttled.has(actionType)) {
11125
12138
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
11126
12139
  }
11127
12140
  const action = this._throttled.get(actionType);
11128
12141
  if (!action)
11129
12142
  throw new Error("invalid throttle");
11130
- const actionPath = action.get(path23);
12143
+ const actionPath = action.get(path24);
11131
12144
  if (actionPath) {
11132
12145
  actionPath.count++;
11133
12146
  return false;
11134
12147
  }
11135
12148
  let timeoutObject;
11136
12149
  const clear = () => {
11137
- const item = action.get(path23);
12150
+ const item = action.get(path24);
11138
12151
  const count = item ? item.count : 0;
11139
- action.delete(path23);
12152
+ action.delete(path24);
11140
12153
  clearTimeout(timeoutObject);
11141
12154
  if (item)
11142
12155
  clearTimeout(item.timeoutObject);
@@ -11144,7 +12157,7 @@ var FSWatcher = class extends EventEmitter2 {
11144
12157
  };
11145
12158
  timeoutObject = setTimeout(clear, timeout);
11146
12159
  const thr = { timeoutObject, clear, count: 0 };
11147
- action.set(path23, thr);
12160
+ action.set(path24, thr);
11148
12161
  return thr;
11149
12162
  }
11150
12163
  _incrReadyCount() {
@@ -11158,44 +12171,44 @@ var FSWatcher = class extends EventEmitter2 {
11158
12171
  * @param event
11159
12172
  * @param awfEmit Callback to be called when ready for event to be emitted.
11160
12173
  */
11161
- _awaitWriteFinish(path23, threshold, event, awfEmit) {
12174
+ _awaitWriteFinish(path24, threshold, event, awfEmit) {
11162
12175
  const awf = this.options.awaitWriteFinish;
11163
12176
  if (typeof awf !== "object")
11164
12177
  return;
11165
12178
  const pollInterval = awf.pollInterval;
11166
12179
  let timeoutHandler;
11167
- let fullPath = path23;
11168
- if (this.options.cwd && !sp2.isAbsolute(path23)) {
11169
- fullPath = sp2.join(this.options.cwd, path23);
12180
+ let fullPath = path24;
12181
+ if (this.options.cwd && !sp2.isAbsolute(path24)) {
12182
+ fullPath = sp2.join(this.options.cwd, path24);
11170
12183
  }
11171
12184
  const now = /* @__PURE__ */ new Date();
11172
12185
  const writes = this._pendingWrites;
11173
12186
  function awaitWriteFinishFn(prevStat) {
11174
12187
  statcb(fullPath, (err, curStat) => {
11175
- if (err || !writes.has(path23)) {
12188
+ if (err || !writes.has(path24)) {
11176
12189
  if (err && err.code !== "ENOENT")
11177
12190
  awfEmit(err);
11178
12191
  return;
11179
12192
  }
11180
12193
  const now2 = Number(/* @__PURE__ */ new Date());
11181
12194
  if (prevStat && curStat.size !== prevStat.size) {
11182
- writes.get(path23).lastChange = now2;
12195
+ writes.get(path24).lastChange = now2;
11183
12196
  }
11184
- const pw = writes.get(path23);
12197
+ const pw = writes.get(path24);
11185
12198
  const df = now2 - pw.lastChange;
11186
12199
  if (df >= threshold) {
11187
- writes.delete(path23);
12200
+ writes.delete(path24);
11188
12201
  awfEmit(void 0, curStat);
11189
12202
  } else {
11190
12203
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
11191
12204
  }
11192
12205
  });
11193
12206
  }
11194
- if (!writes.has(path23)) {
11195
- writes.set(path23, {
12207
+ if (!writes.has(path24)) {
12208
+ writes.set(path24, {
11196
12209
  lastChange: now,
11197
12210
  cancelWait: () => {
11198
- writes.delete(path23);
12211
+ writes.delete(path24);
11199
12212
  clearTimeout(timeoutHandler);
11200
12213
  return event;
11201
12214
  }
@@ -11206,8 +12219,8 @@ var FSWatcher = class extends EventEmitter2 {
11206
12219
  /**
11207
12220
  * Determines whether user has asked to ignore this path.
11208
12221
  */
11209
- _isIgnored(path23, stats) {
11210
- if (this.options.atomic && DOT_RE.test(path23))
12222
+ _isIgnored(path24, stats) {
12223
+ if (this.options.atomic && DOT_RE.test(path24))
11211
12224
  return true;
11212
12225
  if (!this._userIgnored) {
11213
12226
  const { cwd } = this.options;
@@ -11217,17 +12230,17 @@ var FSWatcher = class extends EventEmitter2 {
11217
12230
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11218
12231
  this._userIgnored = anymatch(list, void 0);
11219
12232
  }
11220
- return this._userIgnored(path23, stats);
12233
+ return this._userIgnored(path24, stats);
11221
12234
  }
11222
- _isntIgnored(path23, stat4) {
11223
- return !this._isIgnored(path23, stat4);
12235
+ _isntIgnored(path24, stat4) {
12236
+ return !this._isIgnored(path24, stat4);
11224
12237
  }
11225
12238
  /**
11226
12239
  * Provides a set of common helpers and properties relating to symlink handling.
11227
12240
  * @param path file or directory pattern being watched
11228
12241
  */
11229
- _getWatchHelpers(path23) {
11230
- return new WatchHelper(path23, this.options.followSymlinks, this);
12242
+ _getWatchHelpers(path24) {
12243
+ return new WatchHelper(path24, this.options.followSymlinks, this);
11231
12244
  }
11232
12245
  // Directory helpers
11233
12246
  // -----------------
@@ -11259,63 +12272,63 @@ var FSWatcher = class extends EventEmitter2 {
11259
12272
  * @param item base path of item/directory
11260
12273
  */
11261
12274
  _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))
12275
+ const path24 = sp2.join(directory, item);
12276
+ const fullPath = sp2.resolve(path24);
12277
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
12278
+ if (!this._throttle("remove", path24, 100))
11266
12279
  return;
11267
12280
  if (!isDirectory && this._watched.size === 1) {
11268
12281
  this.add(directory, item, true);
11269
12282
  }
11270
- const wp = this._getWatchedDir(path23);
12283
+ const wp = this._getWatchedDir(path24);
11271
12284
  const nestedDirectoryChildren = wp.getChildren();
11272
- nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
12285
+ nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
11273
12286
  const parent = this._getWatchedDir(directory);
11274
12287
  const wasTracked = parent.has(item);
11275
12288
  parent.remove(item);
11276
12289
  if (this._symlinkPaths.has(fullPath)) {
11277
12290
  this._symlinkPaths.delete(fullPath);
11278
12291
  }
11279
- let relPath = path23;
12292
+ let relPath = path24;
11280
12293
  if (this.options.cwd)
11281
- relPath = sp2.relative(this.options.cwd, path23);
12294
+ relPath = sp2.relative(this.options.cwd, path24);
11282
12295
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11283
12296
  const event = this._pendingWrites.get(relPath).cancelWait();
11284
12297
  if (event === EVENTS.ADD)
11285
12298
  return;
11286
12299
  }
11287
- this._watched.delete(path23);
12300
+ this._watched.delete(path24);
11288
12301
  this._watched.delete(fullPath);
11289
12302
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11290
- if (wasTracked && !this._isIgnored(path23))
11291
- this._emit(eventName, path23);
11292
- this._closePath(path23);
12303
+ if (wasTracked && !this._isIgnored(path24))
12304
+ this._emit(eventName, path24);
12305
+ this._closePath(path24);
11293
12306
  }
11294
12307
  /**
11295
12308
  * Closes all watchers for a path
11296
12309
  */
11297
- _closePath(path23) {
11298
- this._closeFile(path23);
11299
- const dir = sp2.dirname(path23);
11300
- this._getWatchedDir(dir).remove(sp2.basename(path23));
12310
+ _closePath(path24) {
12311
+ this._closeFile(path24);
12312
+ const dir = sp2.dirname(path24);
12313
+ this._getWatchedDir(dir).remove(sp2.basename(path24));
11301
12314
  }
11302
12315
  /**
11303
12316
  * Closes only file-specific watchers
11304
12317
  */
11305
- _closeFile(path23) {
11306
- const closers = this._closers.get(path23);
12318
+ _closeFile(path24) {
12319
+ const closers = this._closers.get(path24);
11307
12320
  if (!closers)
11308
12321
  return;
11309
12322
  closers.forEach((closer) => closer());
11310
- this._closers.delete(path23);
12323
+ this._closers.delete(path24);
11311
12324
  }
11312
- _addPathCloser(path23, closer) {
12325
+ _addPathCloser(path24, closer) {
11313
12326
  if (!closer)
11314
12327
  return;
11315
- let list = this._closers.get(path23);
12328
+ let list = this._closers.get(path24);
11316
12329
  if (!list) {
11317
12330
  list = [];
11318
- this._closers.set(path23, list);
12331
+ this._closers.set(path24, list);
11319
12332
  }
11320
12333
  list.push(closer);
11321
12334
  }
@@ -11345,7 +12358,7 @@ function watch(paths, options = {}) {
11345
12358
  var chokidar_default = { watch, FSWatcher };
11346
12359
 
11347
12360
  // src/watcher/file-watcher.ts
11348
- import * as path16 from "path";
12361
+ import * as path17 from "path";
11349
12362
  var FileWatcher = class {
11350
12363
  watcher = null;
11351
12364
  projectRoot;
@@ -11356,6 +12369,8 @@ var FileWatcher = class {
11356
12369
  debounceTimer = null;
11357
12370
  debounceMs = 1e3;
11358
12371
  onChanges = null;
12372
+ readyPromise = null;
12373
+ resolveReady = null;
11359
12374
  constructor(projectRoot, config, host = "opencode", options = {}) {
11360
12375
  this.projectRoot = projectRoot;
11361
12376
  this.config = config;
@@ -11371,15 +12386,15 @@ var FileWatcher = class {
11371
12386
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
11372
12387
  this.watcher = chokidar_default.watch(watchTargets, {
11373
12388
  ignored: (filePath) => {
11374
- const relativePath = path16.relative(this.projectRoot, filePath);
12389
+ const relativePath = path17.relative(this.projectRoot, filePath);
11375
12390
  if (!relativePath) return false;
11376
12391
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
11377
12392
  return false;
11378
12393
  }
11379
- if (hasFilteredPathSegment(relativePath, path16.sep)) {
12394
+ if (hasFilteredPathSegment(relativePath, path17.sep)) {
11380
12395
  return true;
11381
12396
  }
11382
- if (isRestrictedDirectory(relativePath, path16.sep)) {
12397
+ if (isRestrictedDirectory(relativePath, path17.sep)) {
11383
12398
  return true;
11384
12399
  }
11385
12400
  if (ignoreFilter.ignores(relativePath)) {
@@ -11394,6 +12409,13 @@ var FileWatcher = class {
11394
12409
  pollInterval: 100
11395
12410
  }
11396
12411
  });
12412
+ this.readyPromise = new Promise((resolve13) => {
12413
+ this.resolveReady = resolve13;
12414
+ });
12415
+ this.watcher.once("ready", () => {
12416
+ this.resolveReady?.();
12417
+ this.resolveReady = null;
12418
+ });
11397
12419
  this.watcher.on("error", (error) => {
11398
12420
  const err = error instanceof Error ? error : null;
11399
12421
  if (err?.code === "EPERM" || err?.code === "EACCES") {
@@ -11425,24 +12447,24 @@ var FileWatcher = class {
11425
12447
  this.scheduleFlush();
11426
12448
  }
11427
12449
  isProjectConfigPath(filePath) {
11428
- const relativePath = path16.relative(this.projectRoot, filePath);
11429
- const normalizedRelativePath = path16.normalize(relativePath);
12450
+ const relativePath = path17.relative(this.projectRoot, filePath);
12451
+ const normalizedRelativePath = path17.normalize(relativePath);
11430
12452
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
11431
12453
  }
11432
12454
  isProjectConfigPathOrAncestor(relativePath) {
11433
- const normalizedRelativePath = path16.normalize(relativePath);
12455
+ const normalizedRelativePath = path17.normalize(relativePath);
11434
12456
  return this.getProjectConfigRelativePaths().some(
11435
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path16.sep}`)
12457
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path17.sep}`)
11436
12458
  );
11437
12459
  }
11438
12460
  getProjectConfigRelativePaths() {
11439
12461
  if (this.configPath) {
11440
- return [path16.normalize(path16.relative(this.projectRoot, this.configPath))];
12462
+ return [path17.normalize(path17.relative(this.projectRoot, this.configPath))];
11441
12463
  }
11442
12464
  return [
11443
12465
  resolveProjectConfigPath(this.projectRoot, this.host),
11444
12466
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
11445
- ].map((configPath) => path16.normalize(path16.relative(this.projectRoot, configPath)));
12467
+ ].map((configPath) => path17.normalize(path17.relative(this.projectRoot, configPath)));
11446
12468
  }
11447
12469
  scheduleFlush() {
11448
12470
  if (this.debounceTimer) {
@@ -11457,7 +12479,7 @@ var FileWatcher = class {
11457
12479
  return;
11458
12480
  }
11459
12481
  const changes = Array.from(this.pendingChanges.entries()).map(
11460
- ([path23, type]) => ({ path: path23, type })
12482
+ ([path24, type]) => ({ path: path24, type })
11461
12483
  );
11462
12484
  this.pendingChanges.clear();
11463
12485
  try {
@@ -11466,25 +12488,32 @@ var FileWatcher = class {
11466
12488
  console.error("Error handling file changes:", error);
11467
12489
  }
11468
12490
  }
11469
- stop() {
12491
+ async stop() {
11470
12492
  if (this.debounceTimer) {
11471
12493
  clearTimeout(this.debounceTimer);
11472
12494
  this.debounceTimer = null;
11473
12495
  }
11474
12496
  if (this.watcher) {
11475
- this.watcher.close();
12497
+ const watcher = this.watcher;
11476
12498
  this.watcher = null;
12499
+ await watcher.close();
11477
12500
  }
12501
+ this.resolveReady?.();
12502
+ this.resolveReady = null;
12503
+ this.readyPromise = null;
11478
12504
  this.pendingChanges.clear();
11479
12505
  this.onChanges = null;
11480
12506
  }
11481
12507
  isRunning() {
11482
12508
  return this.watcher !== null;
11483
12509
  }
12510
+ async waitUntilReady() {
12511
+ await (this.readyPromise ?? Promise.resolve());
12512
+ }
11484
12513
  };
11485
12514
 
11486
12515
  // src/watcher/git-head-watcher.ts
11487
- import * as path17 from "path";
12516
+ import * as path18 from "path";
11488
12517
  var GitHeadWatcher = class {
11489
12518
  watcher = null;
11490
12519
  projectRoot;
@@ -11506,7 +12535,7 @@ var GitHeadWatcher = class {
11506
12535
  this.onBranchChange = handler;
11507
12536
  this.currentBranch = getCurrentBranch(this.projectRoot);
11508
12537
  const headPath = getHeadPath(this.projectRoot);
11509
- const refsPath = path17.join(this.projectRoot, ".git", "refs", "heads");
12538
+ const refsPath = path18.join(this.projectRoot, ".git", "refs", "heads");
11510
12539
  this.watcher = chokidar_default.watch([headPath, refsPath], {
11511
12540
  persistent: true,
11512
12541
  ignoreInitial: true,
@@ -11543,14 +12572,15 @@ var GitHeadWatcher = class {
11543
12572
  getCurrentBranch() {
11544
12573
  return this.currentBranch;
11545
12574
  }
11546
- stop() {
12575
+ async stop() {
11547
12576
  if (this.debounceTimer) {
11548
12577
  clearTimeout(this.debounceTimer);
11549
12578
  this.debounceTimer = null;
11550
12579
  }
11551
12580
  if (this.watcher) {
11552
- this.watcher.close();
12581
+ const watcher = this.watcher;
11553
12582
  this.watcher = null;
12583
+ await watcher.close();
11554
12584
  }
11555
12585
  this.onBranchChange = null;
11556
12586
  }
@@ -11568,6 +12598,8 @@ var BackgroundReindexer = class {
11568
12598
  running = false;
11569
12599
  pending = false;
11570
12600
  stopped = false;
12601
+ retryTimer = null;
12602
+ retryDelayMs = 50;
11571
12603
  request() {
11572
12604
  if (this.stopped) {
11573
12605
  return;
@@ -11578,9 +12610,13 @@ var BackgroundReindexer = class {
11578
12610
  stop() {
11579
12611
  this.stopped = true;
11580
12612
  this.pending = false;
12613
+ if (this.retryTimer) {
12614
+ clearTimeout(this.retryTimer);
12615
+ this.retryTimer = null;
12616
+ }
11581
12617
  }
11582
12618
  drain() {
11583
- if (this.stopped || this.running || !this.pending) {
12619
+ if (this.stopped || this.running || this.retryTimer || !this.pending) {
11584
12620
  return;
11585
12621
  }
11586
12622
  this.pending = false;
@@ -11588,13 +12624,29 @@ var BackgroundReindexer = class {
11588
12624
  void this.run();
11589
12625
  }
11590
12626
  async run() {
12627
+ let retryAfterContention = false;
11591
12628
  try {
11592
12629
  await this.runIndex();
12630
+ this.retryDelayMs = 50;
11593
12631
  } catch (error) {
11594
- console.error("[codebase-index] Background reindex failed:", error);
12632
+ if (isTransientIndexLockContention(error)) {
12633
+ this.pending = true;
12634
+ retryAfterContention = true;
12635
+ } else {
12636
+ console.error("[codebase-index] Background reindex failed:", error);
12637
+ }
11595
12638
  } finally {
11596
12639
  this.running = false;
11597
- this.drain();
12640
+ if (retryAfterContention && !this.stopped) {
12641
+ const delay = this.retryDelayMs;
12642
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
12643
+ this.retryTimer = setTimeout(() => {
12644
+ this.retryTimer = null;
12645
+ this.drain();
12646
+ }, delay);
12647
+ } else {
12648
+ this.drain();
12649
+ }
11598
12650
  }
11599
12651
  }
11600
12652
  };
@@ -11628,10 +12680,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
11628
12680
  return {
11629
12681
  fileWatcher,
11630
12682
  gitWatcher,
11631
- stop() {
12683
+ whenReady() {
12684
+ return fileWatcher.waitUntilReady();
12685
+ },
12686
+ async stop() {
11632
12687
  backgroundReindexer.stop();
11633
- fileWatcher.stop();
11634
- gitWatcher?.stop();
12688
+ await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
11635
12689
  }
11636
12690
  };
11637
12691
  }
@@ -11737,13 +12791,13 @@ var pr_impact = tool({
11737
12791
  });
11738
12792
 
11739
12793
  // src/tools/index.ts
11740
- import { writeFileSync as writeFileSync4 } from "fs";
11741
- import * as os4 from "os";
11742
- import * as path20 from "path";
12794
+ import { writeFileSync as writeFileSync5 } from "fs";
12795
+ import * as os5 from "os";
12796
+ import * as path21 from "path";
11743
12797
 
11744
12798
  // src/tools/visualize/activity.ts
11745
12799
  import { execFileSync } from "child_process";
11746
- import * as path18 from "path";
12800
+ import * as path19 from "path";
11747
12801
  function attachRecentActivity(data, projectRoot) {
11748
12802
  const activity = readGitActivity(projectRoot);
11749
12803
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -11905,7 +12959,7 @@ function normalizePath2(filePath) {
11905
12959
  return filePath.replace(/\\/g, "/");
11906
12960
  }
11907
12961
  function toGitRelativePath(projectRoot, filePath) {
11908
- const relativePath = path18.isAbsolute(filePath) ? path18.relative(projectRoot, filePath) : filePath;
12962
+ const relativePath = path19.isAbsolute(filePath) ? path19.relative(projectRoot, filePath) : filePath;
11909
12963
  return normalizePath2(relativePath);
11910
12964
  }
11911
12965
 
@@ -12163,7 +13217,7 @@ render();
12163
13217
  }
12164
13218
 
12165
13219
  // src/tools/visualize/transform.ts
12166
- import * as path19 from "path";
13220
+ import * as path20 from "path";
12167
13221
 
12168
13222
  // src/tools/visualize/modules.ts
12169
13223
  var MAX_MODULES = 18;
@@ -12423,7 +13477,7 @@ function transformForVisualization(symbols, edges, options = {}) {
12423
13477
  filePath: s.filePath,
12424
13478
  kind: s.kind,
12425
13479
  line: s.startLine,
12426
- directory: path19.dirname(s.filePath),
13480
+ directory: path20.dirname(s.filePath),
12427
13481
  moduleId: "",
12428
13482
  moduleLabel: ""
12429
13483
  }));
@@ -12504,7 +13558,9 @@ var index_codebase = tool2({
12504
13558
  const result = await runIndexCodebase(context?.worktree, DEFAULT_HOST, args, (title, metadata) => {
12505
13559
  context.metadata({ title, metadata });
12506
13560
  });
12507
- return result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
13561
+ if (result.kind === "estimate") return formatCostEstimate(result.estimate);
13562
+ if (result.kind === "busy") return result.text;
13563
+ return formatIndexStats(result.stats, args.verbose ?? false);
12508
13564
  }
12509
13565
  });
12510
13566
  var index_status = tool2({
@@ -12518,7 +13574,9 @@ var index_health_check = tool2({
12518
13574
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
12519
13575
  args: {},
12520
13576
  async execute(_args, context) {
12521
- return formatHealthCheck(await getIndexHealthCheck(context?.worktree, DEFAULT_HOST));
13577
+ const result = await runIndexHealthCheck(context?.worktree, DEFAULT_HOST);
13578
+ if (result.kind === "busy") return result.text;
13579
+ return formatHealthCheck(result.health);
12522
13580
  }
12523
13581
  });
12524
13582
  var index_metrics = tool2({
@@ -12638,8 +13696,8 @@ var call_graph_path = tool2({
12638
13696
  maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12639
13697
  },
12640
13698
  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);
13699
+ const path24 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
13700
+ return formatCallGraphPath(args.from, args.to, path24);
12643
13701
  }
12644
13702
  });
12645
13703
  var add_knowledge_base = tool2({
@@ -12692,8 +13750,8 @@ var index_visualize = tool2({
12692
13750
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
12693
13751
  }
12694
13752
  const html = generateVisualizationHtml(vizData);
12695
- const outputPath = path20.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12696
- writeFileSync4(outputPath, html, "utf-8");
13753
+ const outputPath = path21.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
13754
+ writeFileSync5(outputPath, html, "utf-8");
12697
13755
  let result = `Temporal call graph visualization generated: ${outputPath}
12698
13756
 
12699
13757
  `;
@@ -12714,8 +13772,8 @@ var index_visualize = tool2({
12714
13772
  });
12715
13773
 
12716
13774
  // src/commands/loader.ts
12717
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
12718
- import * as path21 from "path";
13775
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
13776
+ import * as path22 from "path";
12719
13777
  function parseFrontmatter(content) {
12720
13778
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12721
13779
  const match = content.match(frontmatterRegex);
@@ -12736,21 +13794,21 @@ function parseFrontmatter(content) {
12736
13794
  }
12737
13795
  function loadCommandsFromDirectory(commandsDir) {
12738
13796
  const commands = /* @__PURE__ */ new Map();
12739
- if (!existsSync10(commandsDir)) {
13797
+ if (!existsSync11(commandsDir)) {
12740
13798
  return commands;
12741
13799
  }
12742
- const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
13800
+ const files = readdirSync3(commandsDir).filter((f) => f.endsWith(".md"));
12743
13801
  for (const file of files) {
12744
- const filePath = path21.join(commandsDir, file);
13802
+ const filePath = path22.join(commandsDir, file);
12745
13803
  let content;
12746
13804
  try {
12747
- content = readFileSync7(filePath, "utf-8");
13805
+ content = readFileSync8(filePath, "utf-8");
12748
13806
  } catch (error) {
12749
13807
  const message = error instanceof Error ? error.message : String(error);
12750
13808
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
12751
13809
  }
12752
13810
  const { frontmatter, body } = parseFrontmatter(content);
12753
- const name = path21.basename(file, ".md");
13811
+ const name = path22.basename(file, ".md");
12754
13812
  const description = frontmatter.description || `Run the ${name} command`;
12755
13813
  commands.set(name, {
12756
13814
  description,
@@ -13054,18 +14112,37 @@ var RoutingHintController = class {
13054
14112
  };
13055
14113
 
13056
14114
  // src/utils/auto-index.ts
14115
+ var INITIAL_RETRY_DELAY_MS = 50;
14116
+ var MAX_RETRY_DELAY_MS = 500;
14117
+ var autoIndexStates = /* @__PURE__ */ new WeakMap();
13057
14118
  function getErrorMessage3(error) {
13058
14119
  return error instanceof Error ? error.message : String(error);
13059
14120
  }
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
- });
14121
+ function runAutoIndex(indexer, projectRoot, state) {
14122
+ void indexer.index().then(() => {
14123
+ autoIndexStates.delete(indexer);
13065
14124
  }).catch((error) => {
13066
- console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14125
+ if (!isTransientIndexLockContention(error)) {
14126
+ autoIndexStates.delete(indexer);
14127
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14128
+ return;
14129
+ }
14130
+ const retryDelayMs = state.retryDelayMs;
14131
+ state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
14132
+ const retryTimer = setTimeout(() => {
14133
+ runAutoIndex(indexer, projectRoot, state);
14134
+ }, retryDelayMs);
14135
+ retryTimer.unref?.();
13067
14136
  });
13068
14137
  }
14138
+ function startAutoIndex(indexer, projectRoot) {
14139
+ if (autoIndexStates.has(indexer)) return;
14140
+ const state = {
14141
+ retryDelayMs: INITIAL_RETRY_DELAY_MS
14142
+ };
14143
+ autoIndexStates.set(indexer, state);
14144
+ runAutoIndex(indexer, projectRoot, state);
14145
+ }
13069
14146
 
13070
14147
  // src/index.ts
13071
14148
  var activeWatchers = /* @__PURE__ */ new Map();
@@ -13082,9 +14159,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
13082
14159
  function getCommandsDir() {
13083
14160
  let currentDir = process.cwd();
13084
14161
  if (typeof import.meta !== "undefined" && import.meta.url) {
13085
- currentDir = path22.dirname(fileURLToPath2(import.meta.url));
14162
+ currentDir = path23.dirname(fileURLToPath2(import.meta.url));
13086
14163
  }
13087
- return path22.join(currentDir, "..", "commands");
14164
+ return path23.join(currentDir, "..", "commands");
13088
14165
  }
13089
14166
  function appendRoutingHints(output, hints, preferredRole) {
13090
14167
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -13104,7 +14181,7 @@ var plugin = async ({ directory, worktree }) => {
13104
14181
  initializeTools2(projectRoot, config);
13105
14182
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
13106
14183
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
13107
- const isHomeDir = path22.resolve(projectRoot) === path22.resolve(os5.homedir());
14184
+ const isHomeDir = path23.resolve(projectRoot) === path23.resolve(os6.homedir());
13108
14185
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
13109
14186
  if (isHomeDir) {
13110
14187
  console.warn(