opencode-codebase-index 0.14.0 → 0.16.0

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