opencode-codebase-index 0.13.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -495,7 +495,7 @@ var require_ignore = __commonJS({
495
495
  // path matching.
496
496
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
497
497
  // @returns {TestResult} true if a file is ignored
498
- test(path22, 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(path22);
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 = (path22, originalPath, doThrow) => {
529
- if (!isString(path22)) {
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 (!path22) {
535
+ if (!path24) {
536
536
  return doThrow(`path must not be empty`, TypeError);
537
537
  }
538
- if (checkPath.isNotRelative(path22)) {
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 = (path22) => REGEX_TEST_INVALID_PATH.test(path22);
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 path22 = originalPath && checkPath.convert(originalPath);
577
+ const path24 = originalPath && checkPath.convert(originalPath);
578
578
  checkPath(
579
- path22,
579
+ path24,
580
580
  originalPath,
581
581
  this._strictPathCheck ? throwError : RETURN_FALSE
582
582
  );
583
- return this._t(path22, cache, checkUnignored, slices);
583
+ return this._t(path24, cache, checkUnignored, slices);
584
584
  }
585
- checkIgnore(path22) {
586
- if (!REGEX_TEST_TRAILING_SLASH.test(path22)) {
587
- return this.test(path22);
585
+ checkIgnore(path24) {
586
+ if (!REGEX_TEST_TRAILING_SLASH.test(path24)) {
587
+ return this.test(path24);
588
588
  }
589
- const slices = path22.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(path22, false, MODE_CHECK_IGNORE);
602
+ return this._rules.test(path24, false, MODE_CHECK_IGNORE);
603
603
  }
604
- _t(path22, cache, checkUnignored, slices) {
605
- if (path22 in cache) {
606
- return cache[path22];
604
+ _t(path24, cache, checkUnignored, slices) {
605
+ if (path24 in cache) {
606
+ return cache[path24];
607
607
  }
608
608
  if (!slices) {
609
- slices = path22.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[path22] = this._rules.test(path22, 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[path22] = parent.ignored ? parent : this._rules.test(path22, checkUnignored, MODE_IGNORE);
621
+ return cache[path24] = parent.ignored ? parent : this._rules.test(path24, checkUnignored, MODE_IGNORE);
622
622
  }
623
- ignores(path22) {
624
- return this._test(path22, this._ignoreCache, false).ignored;
623
+ ignores(path24) {
624
+ return this._test(path24, this._ignoreCache, false).ignored;
625
625
  }
626
626
  createFilter() {
627
- return (path22) => !this.ignores(path22);
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(path22) {
634
- return this._test(path22, 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 = (path22) => checkPath(path22 && checkPath.convert(path22), path22, 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 = (path22) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path22) || isNotRelative(path22);
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 path21 = __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}",
@@ -795,7 +795,8 @@ function getDefaultIndexingConfig() {
795
795
  requireProjectMarker: true,
796
796
  maxDepth: 5,
797
797
  maxFilesPerDirectory: 100,
798
- fallbackToTextOnMaxChunks: true
798
+ fallbackToTextOnMaxChunks: true,
799
+ gitBlame: { enabled: false }
799
800
  };
800
801
  }
801
802
  function getDefaultSearchConfig() {
@@ -919,7 +920,10 @@ function parseConfig(raw) {
919
920
  requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
920
921
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
921
922
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
922
- fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
923
+ fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
924
+ gitBlame: {
925
+ enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
926
+ }
923
927
  };
924
928
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
925
929
  const search = {
@@ -1250,6 +1254,9 @@ function getHostProjectIndexRelativePath(host) {
1250
1254
  function hasHostProjectConfig(projectRoot, host) {
1251
1255
  return (0, import_fs3.existsSync)(path3.join(projectRoot, getProjectConfigRelativePath(host)));
1252
1256
  }
1257
+ function hasHostGlobalConfig(host) {
1258
+ return (0, import_fs3.existsSync)(getGlobalConfigPath(host));
1259
+ }
1253
1260
  function getGlobalIndexPath(host = "opencode") {
1254
1261
  switch (host) {
1255
1262
  case "opencode":
@@ -1289,6 +1296,9 @@ function resolveGlobalIndexPath(host = "opencode") {
1289
1296
  return hostIndexPath;
1290
1297
  }
1291
1298
  if (host !== "opencode") {
1299
+ if (hasHostGlobalConfig(host)) {
1300
+ return hostIndexPath;
1301
+ }
1292
1302
  const legacyIndexPath = getGlobalIndexPath("opencode");
1293
1303
  if ((0, import_fs3.existsSync)(legacyIndexPath)) {
1294
1304
  return legacyIndexPath;
@@ -1587,16 +1597,432 @@ function loadMergedConfig(projectRoot, host = "opencode") {
1587
1597
  return merged;
1588
1598
  }
1589
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
+
1590
2016
  // src/tools/operations.ts
1591
- var import_fs9 = require("fs");
1592
- var path14 = __toESM(require("path"), 1);
2017
+ var import_fs10 = require("fs");
2018
+ var path16 = __toESM(require("path"), 1);
1593
2019
 
1594
2020
  // src/indexer/index.ts
1595
- var import_fs7 = require("fs");
1596
- var path11 = __toESM(require("path"), 1);
2021
+ var import_fs8 = require("fs");
2022
+ var path13 = __toESM(require("path"), 1);
1597
2023
  var import_perf_hooks = require("perf_hooks");
1598
- var import_child_process2 = require("child_process");
1599
- var import_util2 = require("util");
2024
+ var import_child_process3 = require("child_process");
2025
+ var import_util3 = require("util");
1600
2026
 
1601
2027
  // node_modules/eventemitter3/index.mjs
1602
2028
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -2643,17 +3069,17 @@ async function pRetry(input, options = {}) {
2643
3069
  }
2644
3070
 
2645
3071
  // src/embeddings/detector.ts
2646
- var import_fs5 = require("fs");
2647
- var path7 = __toESM(require("path"), 1);
2648
- 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);
2649
3075
  function getOpenCodeAuthPath() {
2650
- return path7.join(os2.homedir(), ".local", "share", "opencode", "auth.json");
3076
+ return path8.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
2651
3077
  }
2652
3078
  function loadOpenCodeAuth() {
2653
3079
  const authPath = getOpenCodeAuthPath();
2654
3080
  try {
2655
- if ((0, import_fs5.existsSync)(authPath)) {
2656
- 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"));
2657
3083
  }
2658
3084
  } catch {
2659
3085
  }
@@ -2875,17 +3301,17 @@ function validateExternalUrl(urlString) {
2875
3301
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2876
3302
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2877
3303
  }
2878
- const hostname = parsed.hostname.toLowerCase();
2879
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2880
- 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})` };
2881
3307
  }
2882
3308
  for (const pattern of BLOCKED_METADATA_IPS) {
2883
- if (pattern.test(hostname)) {
2884
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
3309
+ if (pattern.test(hostname2)) {
3310
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2885
3311
  }
2886
3312
  }
2887
- if (/^169\.254\./.test(hostname)) {
2888
- 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})` };
2889
3315
  }
2890
3316
  return { valid: true };
2891
3317
  }
@@ -3356,8 +3782,8 @@ var SiliconFlowReranker = class {
3356
3782
 
3357
3783
  // src/utils/files.ts
3358
3784
  var import_ignore = __toESM(require_ignore(), 1);
3359
- var import_fs6 = require("fs");
3360
- var path8 = __toESM(require("path"), 1);
3785
+ var import_fs7 = require("fs");
3786
+ var path9 = __toESM(require("path"), 1);
3361
3787
  var PROJECT_MARKERS = [
3362
3788
  ".git",
3363
3789
  "package.json",
@@ -3377,7 +3803,7 @@ var PROJECT_MARKERS = [
3377
3803
  ];
3378
3804
  function hasProjectMarker(projectRoot) {
3379
3805
  for (const marker of PROJECT_MARKERS) {
3380
- if ((0, import_fs6.existsSync)(path8.join(projectRoot, marker))) {
3806
+ if ((0, import_fs7.existsSync)(path9.join(projectRoot, marker))) {
3381
3807
  return true;
3382
3808
  }
3383
3809
  }
@@ -3404,16 +3830,16 @@ function createIgnoreFilter(projectRoot) {
3404
3830
  "**/*build*/**"
3405
3831
  ];
3406
3832
  ig.add(defaultIgnores);
3407
- const gitignorePath = path8.join(projectRoot, ".gitignore");
3408
- if ((0, import_fs6.existsSync)(gitignorePath)) {
3409
- 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");
3410
3836
  ig.add(gitignoreContent);
3411
3837
  }
3412
3838
  return ig;
3413
3839
  }
3414
3840
  function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
3415
- const relativePath = path8.relative(projectRoot, filePath);
3416
- if (hasFilteredPathSegment(relativePath, path8.sep)) {
3841
+ const relativePath = path9.relative(projectRoot, filePath);
3842
+ if (hasFilteredPathSegment(relativePath, path9.sep)) {
3417
3843
  return false;
3418
3844
  }
3419
3845
  if (ignoreFilter.ignores(relativePath)) {
@@ -3447,12 +3873,12 @@ function matchGlob(filePath, pattern) {
3447
3873
  return regex.test(filePath);
3448
3874
  }
3449
3875
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3450
- const entries = await import_fs6.promises.readdir(dir, { withFileTypes: true });
3876
+ const entries = await import_fs7.promises.readdir(dir, { withFileTypes: true });
3451
3877
  const filesInDir = [];
3452
3878
  const subdirs = [];
3453
3879
  for (const entry of entries) {
3454
- const fullPath = path8.join(dir, entry.name);
3455
- const relativePath = path8.relative(projectRoot, fullPath);
3880
+ const fullPath = path9.join(dir, entry.name);
3881
+ const relativePath = path9.relative(projectRoot, fullPath);
3456
3882
  if (isHiddenPathSegment(entry.name)) {
3457
3883
  if (entry.isDirectory()) {
3458
3884
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3472,7 +3898,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3472
3898
  if (entry.isDirectory()) {
3473
3899
  subdirs.push({ fullPath, relativePath });
3474
3900
  } else if (entry.isFile()) {
3475
- const stat4 = await import_fs6.promises.stat(fullPath);
3901
+ const stat4 = await import_fs7.promises.stat(fullPath);
3476
3902
  if (stat4.size > maxFileSize) {
3477
3903
  skipped.push({ path: relativePath, reason: "too_large" });
3478
3904
  continue;
@@ -3501,7 +3927,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3501
3927
  yield f;
3502
3928
  }
3503
3929
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3504
- skipped.push({ path: path8.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3930
+ skipped.push({ path: path9.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
3505
3931
  }
3506
3932
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3507
3933
  if (canRecurse) {
@@ -3541,14 +3967,14 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3541
3967
  if (additionalRoots && additionalRoots.length > 0) {
3542
3968
  const normalizedRoots = /* @__PURE__ */ new Set();
3543
3969
  for (const kbRoot of additionalRoots) {
3544
- const resolved = path8.normalize(
3545
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot, kbRoot)
3970
+ const resolved = path9.normalize(
3971
+ path9.isAbsolute(kbRoot) ? kbRoot : path9.resolve(projectRoot, kbRoot)
3546
3972
  );
3547
3973
  normalizedRoots.add(resolved);
3548
3974
  }
3549
3975
  for (const resolvedKbRoot of normalizedRoots) {
3550
3976
  try {
3551
- const stat4 = await import_fs6.promises.stat(resolvedKbRoot);
3977
+ const stat4 = await import_fs7.promises.stat(resolvedKbRoot);
3552
3978
  if (!stat4.isDirectory()) {
3553
3979
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3554
3980
  continue;
@@ -3949,14 +4375,14 @@ function initializeLogger(config) {
3949
4375
  }
3950
4376
 
3951
4377
  // src/native/index.ts
3952
- var path9 = __toESM(require("path"), 1);
3953
- var os3 = __toESM(require("os"), 1);
4378
+ var path10 = __toESM(require("path"), 1);
4379
+ var os4 = __toESM(require("os"), 1);
3954
4380
  var module2 = __toESM(require("module"), 1);
3955
4381
  var import_url = require("url");
3956
4382
  var import_meta = {};
3957
4383
  function getNativeBinding() {
3958
- const platform2 = os3.platform();
3959
- const arch2 = os3.arch();
4384
+ const platform2 = os4.platform();
4385
+ const arch2 = os4.arch();
3960
4386
  let bindingName;
3961
4387
  if (platform2 === "darwin" && arch2 === "arm64") {
3962
4388
  bindingName = "codebase-index-native.darwin-arm64.node";
@@ -3974,19 +4400,19 @@ function getNativeBinding() {
3974
4400
  let currentDir;
3975
4401
  let requireTarget;
3976
4402
  if (typeof import_meta !== "undefined" && import_meta.url) {
3977
- currentDir = path9.dirname((0, import_url.fileURLToPath)(import_meta.url));
4403
+ currentDir = path10.dirname((0, import_url.fileURLToPath)(import_meta.url));
3978
4404
  requireTarget = import_meta.url;
3979
4405
  } else if (typeof __dirname !== "undefined") {
3980
4406
  currentDir = __dirname;
3981
4407
  requireTarget = __filename;
3982
4408
  } else {
3983
4409
  currentDir = process.cwd();
3984
- requireTarget = path9.join(currentDir, "index.js");
4410
+ requireTarget = path10.join(currentDir, "index.js");
3985
4411
  }
3986
4412
  const normalizedDir = currentDir.replace(/\\/g, "/");
3987
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path9.join("src", "native"));
3988
- const packageRoot = isDevMode ? path9.resolve(currentDir, "../..") : path9.resolve(currentDir, "..");
3989
- 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);
3990
4416
  const require2 = module2.createRequire(requireTarget);
3991
4417
  return require2(nativePath);
3992
4418
  }
@@ -4028,6 +4454,12 @@ function createMockNativeBinding() {
4028
4454
  constructor() {
4029
4455
  throw error;
4030
4456
  }
4457
+ static openReadOnly() {
4458
+ throw error;
4459
+ }
4460
+ static createEmptyReadOnly() {
4461
+ throw error;
4462
+ }
4031
4463
  close() {
4032
4464
  throw error;
4033
4465
  }
@@ -4131,6 +4563,12 @@ var VectorStore = class {
4131
4563
  load() {
4132
4564
  this.inner.load();
4133
4565
  }
4566
+ loadStrict() {
4567
+ this.inner.loadStrict();
4568
+ }
4569
+ hasFingerprint() {
4570
+ return this.inner.hasFingerprint();
4571
+ }
4134
4572
  count() {
4135
4573
  return this.inner.count();
4136
4574
  }
@@ -4413,12 +4851,24 @@ var InvertedIndex = class {
4413
4851
  return this.inner.documentCount();
4414
4852
  }
4415
4853
  };
4416
- var Database = class {
4854
+ var Database = class _Database {
4417
4855
  inner;
4418
4856
  closed = false;
4419
4857
  constructor(dbPath) {
4420
4858
  this.inner = new native.Database(dbPath);
4421
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
+ }
4422
4872
  throwIfClosed() {
4423
4873
  if (this.closed) {
4424
4874
  throw new Error("Database is closed");
@@ -4695,7 +5145,7 @@ var Database = class {
4695
5145
  };
4696
5146
 
4697
5147
  // src/tools/changed-files.ts
4698
- var path10 = __toESM(require("path"), 1);
5148
+ var path11 = __toESM(require("path"), 1);
4699
5149
  var import_child_process = require("child_process");
4700
5150
  var import_util = require("util");
4701
5151
  var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
@@ -4783,9 +5233,9 @@ function normalizeFiles(rawFiles, projectRoot) {
4783
5233
  for (const raw of rawFiles) {
4784
5234
  const trimmed = raw.trim();
4785
5235
  if (!trimmed) continue;
4786
- const absolute = path10.resolve(projectRoot, trimmed);
4787
- const relative10 = path10.relative(projectRoot, absolute);
4788
- const cleaned = relative10.startsWith("./") ? relative10.slice(2) : relative10;
5236
+ const absolute = path11.resolve(projectRoot, trimmed);
5237
+ const relative11 = path11.relative(projectRoot, absolute);
5238
+ const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
4789
5239
  if (!seen.has(cleaned)) {
4790
5240
  seen.add(cleaned);
4791
5241
  result.push(cleaned);
@@ -4794,8 +5244,61 @@ function normalizeFiles(rawFiles, projectRoot) {
4794
5244
  return result;
4795
5245
  }
4796
5246
 
5247
+ // src/indexer/git-blame.ts
5248
+ var import_child_process2 = require("child_process");
5249
+ var path12 = __toESM(require("path"), 1);
5250
+ var import_util2 = require("util");
5251
+ var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
5252
+ function parseGitBlamePorcelain(output) {
5253
+ const commits = /* @__PURE__ */ new Map();
5254
+ let current;
5255
+ for (const line of output.split("\n")) {
5256
+ if (/^[0-9a-f]{40} /.test(line)) {
5257
+ const sha = line.slice(0, 40);
5258
+ current = commits.get(sha) ?? {
5259
+ sha,
5260
+ author: "",
5261
+ authorEmail: "",
5262
+ committedAt: 0,
5263
+ summary: "",
5264
+ lines: 0
5265
+ };
5266
+ commits.set(sha, current);
5267
+ continue;
5268
+ }
5269
+ if (!current) {
5270
+ continue;
5271
+ }
5272
+ if (line.startsWith("author ")) {
5273
+ current.author = line.slice("author ".length);
5274
+ } else if (line.startsWith("author-mail ")) {
5275
+ current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
5276
+ } else if (line.startsWith("author-time ")) {
5277
+ current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
5278
+ } else if (line.startsWith("summary ")) {
5279
+ current.summary = line.slice("summary ".length);
5280
+ } else if (line.startsWith(" ")) {
5281
+ current.lines += 1;
5282
+ }
5283
+ }
5284
+ return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
5285
+ }
5286
+ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
5287
+ const relativePath = path12.relative(projectRoot, filePath);
5288
+ try {
5289
+ const { stdout } = await execFileAsync2(
5290
+ "git",
5291
+ ["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
5292
+ { cwd: projectRoot, timeout: 3e4 }
5293
+ );
5294
+ return parseGitBlamePorcelain(stdout);
5295
+ } catch {
5296
+ return void 0;
5297
+ }
5298
+ }
5299
+
4797
5300
  // src/indexer/index.ts
4798
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
5301
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4799
5302
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
4800
5303
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4801
5304
  "function_declaration",
@@ -4875,6 +5378,46 @@ function isSqliteCorruptionError(error) {
4875
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");
4876
5379
  }
4877
5380
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5381
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
5382
+ function metadataFromBlame(blame) {
5383
+ if (!blame) {
5384
+ return {};
5385
+ }
5386
+ return {
5387
+ blameSha: blame.sha,
5388
+ blameAuthor: blame.author,
5389
+ blameAuthorEmail: blame.authorEmail,
5390
+ blameCommittedAt: blame.committedAt,
5391
+ blameSummary: blame.summary
5392
+ };
5393
+ }
5394
+ function blameFromChunkData(chunk) {
5395
+ if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
5396
+ return void 0;
5397
+ }
5398
+ return {
5399
+ sha: chunk.blameSha,
5400
+ author: chunk.blameAuthor,
5401
+ authorEmail: chunk.blameAuthorEmail,
5402
+ committedAt: chunk.blameCommittedAt,
5403
+ summary: chunk.blameSummary
5404
+ };
5405
+ }
5406
+ function blameFromMetadata(metadata) {
5407
+ if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
5408
+ return void 0;
5409
+ }
5410
+ return {
5411
+ sha: metadata.blameSha,
5412
+ author: metadata.blameAuthor,
5413
+ authorEmail: metadata.blameAuthorEmail,
5414
+ committedAt: metadata.blameCommittedAt,
5415
+ summary: metadata.blameSummary
5416
+ };
5417
+ }
5418
+ function hasBlameMetadata(metadata) {
5419
+ return blameFromMetadata(metadata) !== void 0;
5420
+ }
4878
5421
  var INDEX_METADATA_VERSION = "1";
4879
5422
  var EMBEDDING_STRATEGY_VERSION = "2";
4880
5423
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
@@ -5033,9 +5576,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5033
5576
  return true;
5034
5577
  }
5035
5578
  function isPathWithinRoot(filePath, rootPath) {
5036
- const normalizedFilePath = path11.resolve(filePath);
5037
- const normalizedRoot = path11.resolve(rootPath);
5038
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5579
+ const normalizedFilePath = path13.resolve(filePath);
5580
+ const normalizedRoot = path13.resolve(rootPath);
5581
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5039
5582
  }
5040
5583
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5041
5584
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5794,7 +6337,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
5794
6337
  chunkType,
5795
6338
  name: chunk.name ?? void 0,
5796
6339
  language: chunk.language,
5797
- hash: chunk.contentHash
6340
+ hash: chunk.contentHash,
6341
+ ...metadataFromBlame(blameFromChunkData(chunk))
5798
6342
  };
5799
6343
  const baselineScore = existing?.score ?? 0.5;
5800
6344
  candidateUnion.set(chunk.chunkId, {
@@ -5878,7 +6422,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
5878
6422
  chunkType,
5879
6423
  name: chunk.name ?? void 0,
5880
6424
  language: chunk.language,
5881
- hash: chunk.contentHash
6425
+ hash: chunk.contentHash,
6426
+ ...metadataFromBlame(blameFromChunkData(chunk))
5882
6427
  }
5883
6428
  });
5884
6429
  }
@@ -6047,6 +6592,21 @@ function matchesSearchFilters(candidate, options, minScore) {
6047
6592
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
6048
6593
  return false;
6049
6594
  }
6595
+ if (options?.blameAuthor) {
6596
+ const author = options.blameAuthor.toLowerCase();
6597
+ const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
6598
+ const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
6599
+ if (candidateAuthor !== author && candidateEmail !== author) return false;
6600
+ }
6601
+ if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
6602
+ return false;
6603
+ }
6604
+ if (options?.blameSince) {
6605
+ const sinceMs = Date.parse(options.blameSince);
6606
+ if (Number.isNaN(sinceMs)) return false;
6607
+ const committedAt = candidate.metadata.blameCommittedAt;
6608
+ if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
6609
+ }
6050
6610
  return true;
6051
6611
  }
6052
6612
  function unionCandidates(semanticCandidates, keywordCandidates) {
@@ -6084,26 +6644,133 @@ var Indexer = class {
6084
6644
  queryCacheTtlMs = 5 * 60 * 1e3;
6085
6645
  querySimilarityThreshold = 0.85;
6086
6646
  indexCompatibility = null;
6087
- 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();
6088
6655
  constructor(projectRoot, config, host = "opencode") {
6089
6656
  this.projectRoot = projectRoot;
6090
6657
  this.config = config;
6091
6658
  this.host = host;
6092
6659
  this.indexPath = this.getIndexPath();
6093
- this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6094
- this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6095
- this.indexingLockPath = path11.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");
6096
6662
  this.logger = initializeLogger(config.debug);
6097
6663
  }
6098
6664
  getIndexPath() {
6099
6665
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6100
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
+ }
6101
6768
  loadFileHashCache() {
6102
- if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
6769
+ if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
6103
6770
  return;
6104
6771
  }
6105
6772
  try {
6106
- const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
6773
+ const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
6107
6774
  const parsed = JSON.parse(data);
6108
6775
  this.fileHashCache = new Map(Object.entries(parsed));
6109
6776
  } catch (error) {
@@ -6123,15 +6790,26 @@ var Indexer = class {
6123
6790
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6124
6791
  }
6125
6792
  atomicWriteSync(targetPath, data) {
6126
- const tempPath = `${targetPath}.tmp`;
6127
- (0, import_fs7.mkdirSync)(path11.dirname(targetPath), { recursive: true });
6128
- (0, import_fs7.writeFileSync)(tempPath, data);
6129
- (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
+ );
6130
6808
  }
6131
6809
  getScopedRoots() {
6132
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6810
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6133
6811
  for (const kbRoot of this.config.knowledgeBases) {
6134
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6812
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
6135
6813
  }
6136
6814
  return Array.from(roots);
6137
6815
  }
@@ -6140,29 +6818,29 @@ var Indexer = class {
6140
6818
  if (this.config.scope !== "global") {
6141
6819
  return branchName;
6142
6820
  }
6143
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6821
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6144
6822
  return `${projectHash}:${branchName}`;
6145
6823
  }
6146
6824
  getBranchCatalogKeyFor(branchName) {
6147
6825
  if (this.config.scope !== "global") {
6148
6826
  return branchName;
6149
6827
  }
6150
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6828
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6151
6829
  return `${projectHash}:${branchName}`;
6152
6830
  }
6153
6831
  getLegacyBranchCatalogKey() {
6154
6832
  return this.currentBranch || "default";
6155
6833
  }
6156
6834
  getLegacyMigrationMetadataKey() {
6157
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6835
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6158
6836
  return `index.globalBranchMigration.${projectHash}`;
6159
6837
  }
6160
6838
  getProjectEmbeddingStrategyMetadataKey() {
6161
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6839
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6162
6840
  return `index.embeddingStrategyVersion.${projectHash}`;
6163
6841
  }
6164
6842
  getProjectForceReembedMetadataKey() {
6165
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6843
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6166
6844
  return `index.forceReembed.${projectHash}`;
6167
6845
  }
6168
6846
  hasProjectForceReembedPending() {
@@ -6256,7 +6934,7 @@ var Indexer = class {
6256
6934
  if (!this.database) {
6257
6935
  return { chunkIds, symbolIds };
6258
6936
  }
6259
- const projectRootPath = path11.resolve(this.projectRoot);
6937
+ const projectRootPath = path13.resolve(this.projectRoot);
6260
6938
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6261
6939
  ...Array.from(this.fileHashCache.keys()).filter(
6262
6940
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6279,7 +6957,7 @@ var Indexer = class {
6279
6957
  if (this.config.scope !== "global") {
6280
6958
  return this.getBranchCatalogCleanupKeys();
6281
6959
  }
6282
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6960
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6283
6961
  const keys = /* @__PURE__ */ new Set();
6284
6962
  const projectChunkIdSet = new Set(projectChunkIds);
6285
6963
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6363,7 +7041,7 @@ var Indexer = class {
6363
7041
  if (!this.database || this.config.scope !== "global") {
6364
7042
  return false;
6365
7043
  }
6366
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7044
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6367
7045
  const roots = this.getScopedRoots();
6368
7046
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6369
7047
  return this.database.getAllBranches().some(
@@ -6397,7 +7075,7 @@ var Indexer = class {
6397
7075
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6398
7076
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6399
7077
  ]);
6400
- const projectRootPath = path11.resolve(this.projectRoot);
7078
+ const projectRootPath = path13.resolve(this.projectRoot);
6401
7079
  const projectLocalFilePaths = new Set(
6402
7080
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6403
7081
  );
@@ -6459,34 +7137,27 @@ var Indexer = class {
6459
7137
  database.gcOrphanEmbeddings();
6460
7138
  database.gcOrphanChunks();
6461
7139
  store.save();
6462
- invertedIndex.save();
7140
+ this.saveInvertedIndex(invertedIndex);
6463
7141
  return {
6464
7142
  removedChunkIds: removedChunkIdList,
6465
7143
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6466
7144
  };
6467
7145
  }
6468
- checkForInterruptedIndexing() {
6469
- return (0, import_fs7.existsSync)(this.indexingLockPath);
6470
- }
6471
- acquireIndexingLock() {
6472
- const lockData = {
6473
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6474
- pid: process.pid
6475
- };
6476
- (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
6477
- }
6478
- releaseIndexingLock() {
6479
- if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
6480
- (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
+ });
6481
7154
  }
6482
- }
6483
- async recoverFromInterruptedIndexing() {
6484
- this.logger.warn("Detected interrupted indexing session, recovering...");
6485
- if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
6486
- (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();
6487
7160
  }
6488
- await this.healthCheck();
6489
- this.releaseIndexingLock();
6490
7161
  this.logger.info("Recovery complete, next index will re-process all files");
6491
7162
  }
6492
7163
  loadFailedBatches(maxChunkTokens) {
@@ -6502,10 +7173,10 @@ var Indexer = class {
6502
7173
  }
6503
7174
  }
6504
7175
  loadSerializedFailedBatches() {
6505
- if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
7176
+ if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
6506
7177
  return [];
6507
7178
  }
6508
- const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
7179
+ const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
6509
7180
  const parsed = JSON.parse(data);
6510
7181
  return parsed.map((batch) => {
6511
7182
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6522,15 +7193,15 @@ var Indexer = class {
6522
7193
  }
6523
7194
  saveFailedBatches(batches) {
6524
7195
  if (batches.length === 0) {
6525
- if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
7196
+ if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
6526
7197
  try {
6527
- (0, import_fs7.unlinkSync)(this.failedBatchesPath);
7198
+ (0, import_fs8.unlinkSync)(this.failedBatchesPath);
6528
7199
  } catch {
6529
7200
  }
6530
7201
  }
6531
7202
  return;
6532
7203
  }
6533
- (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7204
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6534
7205
  }
6535
7206
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6536
7207
  const retryableById = /* @__PURE__ */ new Map();
@@ -6710,7 +7381,7 @@ var Indexer = class {
6710
7381
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
6711
7382
  parts.push(`intent_hint: ${intent}`);
6712
7383
  try {
6713
- 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");
6714
7385
  const lines = fileContent.split("\n");
6715
7386
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
6716
7387
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -6724,9 +7395,225 @@ var Indexer = class {
6724
7395
  return parts.join("\n");
6725
7396
  }
6726
7397
  async initialize() {
6727
- if (this.config.embeddingProvider === "custom") {
6728
- if (!this.config.customProvider) {
6729
- throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
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();
7614
+ if (this.config.embeddingProvider === "custom") {
7615
+ if (!this.config.customProvider) {
7616
+ throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
6730
7617
  }
6731
7618
  this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
6732
7619
  } else if (this.config.embeddingProvider === "auto") {
@@ -6755,36 +7642,103 @@ var Indexer = class {
6755
7642
  });
6756
7643
  }
6757
7644
  }
6758
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6759
7645
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6760
- const storePath = path11.join(this.indexPath, "vectors");
6761
- this.store = new VectorStore(storePath, dimensions);
6762
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6763
- if ((0, import_fs7.existsSync)(indexFilePath)) {
6764
- this.store.load();
6765
- }
6766
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6767
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6768
- try {
6769
- this.invertedIndex.load();
6770
- } catch {
6771
- if ((0, import_fs7.existsSync)(invertedIndexPath)) {
6772
- 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();
6773
7671
  }
6774
7672
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6775
- }
6776
- const dbPath = path11.join(this.indexPath, "codebase.db");
6777
- let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6778
- try {
6779
- this.database = new Database(dbPath);
6780
- } catch (error) {
6781
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6782
- 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);
6783
7680
  }
7681
+ try {
7682
+ this.database = new Database(dbPath);
7683
+ } catch (error) {
7684
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7685
+ throw error;
7686
+ }
7687
+ this.store = new VectorStore(storePath, dimensions);
7688
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7689
+ this.database = new Database(dbPath);
7690
+ dbIsNew = true;
7691
+ }
7692
+ } else {
6784
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
+ }
6785
7707
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6786
- this.database = new Database(dbPath);
6787
- 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
+ }
6788
7742
  }
6789
7743
  if (isGitRepo(this.projectRoot)) {
6790
7744
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6798,10 +7752,10 @@ var Indexer = class {
6798
7752
  this.baseBranch = "default";
6799
7753
  this.logger.branch("debug", "Not a git repository, using default branch");
6800
7754
  }
6801
- if (this.checkForInterruptedIndexing()) {
6802
- await this.recoverFromInterruptedIndexing();
7755
+ if (mode === "writer" && recoveredOwners.length > 0) {
7756
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6803
7757
  }
6804
- if (dbIsNew && this.store.count() > 0) {
7758
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6805
7759
  this.migrateFromLegacyIndex();
6806
7760
  }
6807
7761
  this.loadFileHashCache();
@@ -6813,9 +7767,11 @@ var Indexer = class {
6813
7767
  configuredProviderInfo: this.configuredProviderInfo
6814
7768
  });
6815
7769
  }
6816
- if (this.config.indexing.autoGc) {
7770
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6817
7771
  await this.maybeRunAutoGc();
6818
7772
  }
7773
+ this.initializationMode = mode;
7774
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6819
7775
  }
6820
7776
  async maybeRunAutoGc() {
6821
7777
  if (!this.database) return;
@@ -6832,7 +7788,7 @@ var Indexer = class {
6832
7788
  }
6833
7789
  }
6834
7790
  if (shouldRunGc) {
6835
- const result = await this.healthCheck();
7791
+ const result = await this.healthCheckUnlocked();
6836
7792
  if (result.warning) {
6837
7793
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
6838
7794
  } else {
@@ -6854,7 +7810,7 @@ var Indexer = class {
6854
7810
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6855
7811
  return {
6856
7812
  resetCorruptedIndex: true,
6857
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7813
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
6858
7814
  };
6859
7815
  }
6860
7816
  throw error;
@@ -6869,28 +7825,29 @@ var Indexer = class {
6869
7825
  return;
6870
7826
  }
6871
7827
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6872
- const storeBasePath = path11.join(this.indexPath, "vectors");
6873
- const storeIndexPath = `${storeBasePath}.usearch`;
7828
+ const storeBasePath = path13.join(this.indexPath, "vectors");
7829
+ const storeIndexPath = storeBasePath;
6874
7830
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6875
- const backupIndexPath = `${storeIndexPath}.bak`;
6876
- 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");
6877
7834
  let backedUpIndex = false;
6878
7835
  let backedUpMetadata = false;
6879
7836
  let rebuiltCount = 0;
6880
7837
  let skippedCount = 0;
6881
- if ((0, import_fs7.existsSync)(backupIndexPath)) {
6882
- (0, import_fs7.unlinkSync)(backupIndexPath);
7838
+ if ((0, import_fs8.existsSync)(backupIndexPath)) {
7839
+ (0, import_fs8.unlinkSync)(backupIndexPath);
6883
7840
  }
6884
- if ((0, import_fs7.existsSync)(backupMetadataPath)) {
6885
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7841
+ if ((0, import_fs8.existsSync)(backupMetadataPath)) {
7842
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
6886
7843
  }
6887
7844
  try {
6888
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
6889
- (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
7845
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7846
+ (0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
6890
7847
  backedUpIndex = true;
6891
7848
  }
6892
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
6893
- (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
7849
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7850
+ (0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
6894
7851
  backedUpMetadata = true;
6895
7852
  }
6896
7853
  store.clear();
@@ -6910,11 +7867,11 @@ var Indexer = class {
6910
7867
  rebuiltCount += 1;
6911
7868
  }
6912
7869
  store.save();
6913
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
6914
- (0, import_fs7.unlinkSync)(backupIndexPath);
7870
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7871
+ (0, import_fs8.unlinkSync)(backupIndexPath);
6915
7872
  }
6916
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
6917
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7873
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7874
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
6918
7875
  }
6919
7876
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
6920
7877
  excludedChunks: excludedSet.size,
@@ -6926,17 +7883,17 @@ var Indexer = class {
6926
7883
  store.clear();
6927
7884
  } catch {
6928
7885
  }
6929
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
6930
- (0, import_fs7.unlinkSync)(storeIndexPath);
7886
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7887
+ (0, import_fs8.unlinkSync)(storeIndexPath);
6931
7888
  }
6932
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
6933
- (0, import_fs7.unlinkSync)(storeMetadataPath);
7889
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7890
+ (0, import_fs8.unlinkSync)(storeMetadataPath);
6934
7891
  }
6935
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
6936
- (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
7892
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7893
+ (0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
6937
7894
  }
6938
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
6939
- (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
7895
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7896
+ (0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
6940
7897
  }
6941
7898
  if (backedUpIndex || backedUpMetadata) {
6942
7899
  store.load();
@@ -6950,11 +7907,37 @@ var Indexer = class {
6950
7907
  }
6951
7908
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
6952
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
+ }
6953
7936
  async tryResetCorruptedIndex(stage, error) {
6954
7937
  if (!isSqliteCorruptionError(error)) {
6955
7938
  return false;
6956
7939
  }
6957
- const dbPath = path11.join(this.indexPath, "codebase.db");
7940
+ const dbPath = path13.join(this.indexPath, "codebase.db");
6958
7941
  const warning = this.getCorruptedIndexWarning(dbPath);
6959
7942
  const errorMessage = getErrorMessage2(error);
6960
7943
  if (this.config.scope === "global") {
@@ -6970,30 +7953,7 @@ var Indexer = class {
6970
7953
  dbPath,
6971
7954
  error: errorMessage
6972
7955
  });
6973
- this.store = null;
6974
- this.invertedIndex = null;
6975
- this.database?.close();
6976
- this.database = null;
6977
- this.indexCompatibility = null;
6978
- this.fileHashCache.clear();
6979
- const resetPaths = [
6980
- path11.join(this.indexPath, "codebase.db"),
6981
- path11.join(this.indexPath, "codebase.db-shm"),
6982
- path11.join(this.indexPath, "codebase.db-wal"),
6983
- path11.join(this.indexPath, "vectors.usearch"),
6984
- path11.join(this.indexPath, "inverted-index.json"),
6985
- path11.join(this.indexPath, "file-hashes.json"),
6986
- path11.join(this.indexPath, "failed-batches.json"),
6987
- path11.join(this.indexPath, "indexing.lock"),
6988
- path11.join(this.indexPath, "vectors")
6989
- ];
6990
- await Promise.all(resetPaths.map(async (targetPath) => {
6991
- try {
6992
- await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
6993
- } catch {
6994
- }
6995
- }));
6996
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
7956
+ await this.resetLocalIndexArtifacts();
6997
7957
  return true;
6998
7958
  }
6999
7959
  migrateFromLegacyIndex() {
@@ -7112,8 +8072,62 @@ var Indexer = class {
7112
8072
  return this.indexCompatibility;
7113
8073
  }
7114
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() {
7115
8129
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7116
- await this.initialize();
8130
+ throw new Error("Index state is not initialized");
7117
8131
  }
7118
8132
  return {
7119
8133
  store: this.store,
@@ -7137,7 +8151,12 @@ var Indexer = class {
7137
8151
  return createCostEstimate(files, configuredProviderInfo);
7138
8152
  }
7139
8153
  async index(onProgress) {
7140
- 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);
7141
8160
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7142
8161
  const branchCatalogKey = this.getBranchCatalogKey();
7143
8162
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7147,7 +8166,6 @@ var Indexer = class {
7147
8166
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7148
8167
  );
7149
8168
  }
7150
- this.acquireIndexingLock();
7151
8169
  this.logger.recordIndexingStart();
7152
8170
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7153
8171
  const startTime = Date.now();
@@ -7198,7 +8216,7 @@ var Indexer = class {
7198
8216
  unchangedFilePaths.add(f.path);
7199
8217
  this.logger.recordCacheHit();
7200
8218
  } else {
7201
- const content = await import_fs7.promises.readFile(f.path, "utf-8");
8219
+ const content = await import_fs8.promises.readFile(f.path, "utf-8");
7202
8220
  changedFiles.push({ path: f.path, content, hash: currentHash });
7203
8221
  this.logger.recordCacheMiss();
7204
8222
  }
@@ -7222,6 +8240,7 @@ var Indexer = class {
7222
8240
  this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
7223
8241
  const existingChunks = /* @__PURE__ */ new Map();
7224
8242
  const existingChunksByFile = /* @__PURE__ */ new Map();
8243
+ const existingMetadataById = /* @__PURE__ */ new Map();
7225
8244
  for (const { key, metadata } of store.getAllMetadata()) {
7226
8245
  if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
7227
8246
  continue;
@@ -7230,6 +8249,7 @@ var Indexer = class {
7230
8249
  continue;
7231
8250
  }
7232
8251
  existingChunks.set(key, metadata.hash);
8252
+ existingMetadataById.set(key, metadata);
7233
8253
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7234
8254
  fileChunks.add(key);
7235
8255
  existingChunksByFile.set(metadata.filePath, fileChunks);
@@ -7237,6 +8257,8 @@ var Indexer = class {
7237
8257
  const currentChunkIds = /* @__PURE__ */ new Set();
7238
8258
  const currentFilePaths = /* @__PURE__ */ new Set();
7239
8259
  const pendingChunks = [];
8260
+ const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
8261
+ let backfilledBlameMetadata = false;
7240
8262
  for (const filePath of unchangedFilePaths) {
7241
8263
  currentFilePaths.add(filePath);
7242
8264
  const fileChunks = existingChunksByFile.get(filePath);
@@ -7247,10 +8269,52 @@ var Indexer = class {
7247
8269
  }
7248
8270
  }
7249
8271
  const chunkDataBatch = [];
8272
+ if (gitBlameEnabled) {
8273
+ const backfillItems = [];
8274
+ for (const chunkId of currentChunkIds) {
8275
+ const metadata = existingMetadataById.get(chunkId);
8276
+ if (!metadata || hasBlameMetadata(metadata)) {
8277
+ continue;
8278
+ }
8279
+ const chunk = database.getChunk(chunkId);
8280
+ if (!chunk) {
8281
+ continue;
8282
+ }
8283
+ const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
8284
+ const blameMetadata = metadataFromBlame(blame);
8285
+ if (!blameMetadata.blameSha) {
8286
+ continue;
8287
+ }
8288
+ chunkDataBatch.push({
8289
+ ...chunk,
8290
+ blameSha: blameMetadata.blameSha,
8291
+ blameAuthor: blameMetadata.blameAuthor,
8292
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8293
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8294
+ blameSummary: blameMetadata.blameSummary
8295
+ });
8296
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8297
+ if (!embeddingBuffer) {
8298
+ continue;
8299
+ }
8300
+ backfillItems.push({
8301
+ id: chunkId,
8302
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
8303
+ metadata: {
8304
+ ...metadata,
8305
+ ...blameMetadata
8306
+ }
8307
+ });
8308
+ }
8309
+ if (backfillItems.length > 0) {
8310
+ store.addBatch(backfillItems);
8311
+ backfilledBlameMetadata = true;
8312
+ }
8313
+ }
7250
8314
  for (const parsed of parsedFiles) {
7251
8315
  currentFilePaths.add(parsed.path);
7252
8316
  if (parsed.chunks.length === 0) {
7253
- const relativePath = path11.relative(this.projectRoot, parsed.path);
8317
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
7254
8318
  stats.parseFailures.push(relativePath);
7255
8319
  }
7256
8320
  let fileChunkCount = 0;
@@ -7271,6 +8335,10 @@ var Indexer = class {
7271
8335
  }
7272
8336
  const id = generateChunkId(parsed.path, chunk);
7273
8337
  const contentHash = generateChunkHash(chunk);
8338
+ const existingContentHash = existingChunks.get(id);
8339
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
8340
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
8341
+ const blameMetadata = metadataFromBlame(blame);
7274
8342
  currentChunkIds.add(id);
7275
8343
  chunkDataBatch.push({
7276
8344
  chunkId: id,
@@ -7280,9 +8348,14 @@ var Indexer = class {
7280
8348
  endLine: chunk.endLine,
7281
8349
  nodeType: chunk.chunkType,
7282
8350
  name: chunk.name,
7283
- language: chunk.language
8351
+ language: chunk.language,
8352
+ blameSha: blameMetadata.blameSha,
8353
+ blameAuthor: blameMetadata.blameAuthor,
8354
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8355
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8356
+ blameSummary: blameMetadata.blameSummary
7284
8357
  });
7285
- if (existingChunks.get(id) === contentHash) {
8358
+ if (existingContentHash === contentHash) {
7286
8359
  fileChunkCount++;
7287
8360
  continue;
7288
8361
  }
@@ -7297,7 +8370,8 @@ var Indexer = class {
7297
8370
  chunkType: chunk.chunkType,
7298
8371
  name: chunk.name,
7299
8372
  language: chunk.language,
7300
- hash: contentHash
8373
+ hash: contentHash,
8374
+ ...blameMetadata
7301
8375
  };
7302
8376
  pendingChunks.push({
7303
8377
  id,
@@ -7440,6 +8514,11 @@ var Indexer = class {
7440
8514
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7441
8515
  database.clearBranchSymbols(branchCatalogKey);
7442
8516
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
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) {
8520
+ store.save();
8521
+ }
7443
8522
  if (scopedRoots) {
7444
8523
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7445
8524
  this.clearScopedFailedBatches(scopedRoots);
@@ -7458,7 +8537,6 @@ var Indexer = class {
7458
8537
  chunksProcessed: 0,
7459
8538
  totalChunks: 0
7460
8539
  });
7461
- this.releaseIndexingLock();
7462
8540
  return stats;
7463
8541
  }
7464
8542
  if (pendingChunks.length === 0) {
@@ -7467,7 +8545,7 @@ var Indexer = class {
7467
8545
  database.clearBranchSymbols(branchCatalogKey);
7468
8546
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7469
8547
  store.save();
7470
- invertedIndex.save();
8548
+ this.saveInvertedIndex(invertedIndex);
7471
8549
  if (scopedRoots) {
7472
8550
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7473
8551
  this.clearScopedFailedBatches(scopedRoots);
@@ -7486,7 +8564,6 @@ var Indexer = class {
7486
8564
  chunksProcessed: 0,
7487
8565
  totalChunks: 0
7488
8566
  });
7489
- this.releaseIndexingLock();
7490
8567
  return stats;
7491
8568
  }
7492
8569
  onProgress?.({
@@ -7717,7 +8794,7 @@ var Indexer = class {
7717
8794
  database.clearBranchSymbols(branchCatalogKey);
7718
8795
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7719
8796
  store.save();
7720
- invertedIndex.save();
8797
+ this.saveInvertedIndex(invertedIndex);
7721
8798
  if (scopedRoots) {
7722
8799
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7723
8800
  } else {
@@ -7769,7 +8846,6 @@ var Indexer = class {
7769
8846
  chunksProcessed: stats.indexedChunks,
7770
8847
  totalChunks: pendingChunks.length
7771
8848
  });
7772
- this.releaseIndexingLock();
7773
8849
  return stats;
7774
8850
  }
7775
8851
  async getQueryEmbedding(query, provider) {
@@ -7835,8 +8911,8 @@ var Indexer = class {
7835
8911
  return intersection / union;
7836
8912
  }
7837
8913
  async search(query, limit, options) {
7838
- const { store, provider, database } = await this.ensureInitialized();
7839
- const compatibility = this.checkCompatibility();
8914
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8915
+ this.requireReadableComponents(readIssues, "vectors", "database");
7840
8916
  if (!compatibility.compatible) {
7841
8917
  throw new Error(
7842
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.`
@@ -7850,6 +8926,7 @@ var Indexer = class {
7850
8926
  const maxResults = limit ?? this.config.search.maxResults;
7851
8927
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
7852
8928
  const fusionStrategy = this.config.search.fusionStrategy;
8929
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
7853
8930
  const rrfK = this.config.search.rrfK;
7854
8931
  const rerankTopN = this.config.search.rerankTopN;
7855
8932
  const filterByBranch = options?.filterByBranch ?? true;
@@ -7858,7 +8935,7 @@ var Indexer = class {
7858
8935
  this.logger.search("debug", "Starting search", {
7859
8936
  query,
7860
8937
  maxResults,
7861
- hybridWeight,
8938
+ hybridWeight: effectiveHybridWeight,
7862
8939
  fusionStrategy,
7863
8940
  rrfK,
7864
8941
  rerankTopN,
@@ -7872,7 +8949,7 @@ var Indexer = class {
7872
8949
  const semanticResults = store.search(embedding, maxResults * 4);
7873
8950
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
7874
8951
  const keywordStartTime = import_perf_hooks.performance.now();
7875
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8952
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
7876
8953
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
7877
8954
  let branchChunkIds = null;
7878
8955
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -7909,7 +8986,7 @@ var Indexer = class {
7909
8986
  rrfK,
7910
8987
  rerankTopN,
7911
8988
  limit: maxResults,
7912
- hybridWeight,
8989
+ hybridWeight: effectiveHybridWeight,
7913
8990
  prioritizeSourcePaths: sourceIntent
7914
8991
  });
7915
8992
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -7983,7 +9060,7 @@ var Indexer = class {
7983
9060
  let contextEndLine = r.metadata.endLine;
7984
9061
  if (!metadataOnly && this.config.search.includeContext) {
7985
9062
  try {
7986
- const fileContent = await import_fs7.promises.readFile(
9063
+ const fileContent = await import_fs8.promises.readFile(
7987
9064
  r.metadata.filePath,
7988
9065
  "utf-8"
7989
9066
  );
@@ -8003,13 +9080,13 @@ var Indexer = class {
8003
9080
  content,
8004
9081
  score: r.score,
8005
9082
  chunkType: r.metadata.chunkType,
8006
- name: r.metadata.name
9083
+ name: r.metadata.name,
9084
+ blame: blameFromMetadata(r.metadata)
8007
9085
  };
8008
9086
  })
8009
9087
  );
8010
9088
  }
8011
- async keywordSearch(query, limit) {
8012
- const { store, invertedIndex } = await this.ensureInitialized();
9089
+ async keywordSearch(query, limit, store, invertedIndex) {
8013
9090
  const scores = invertedIndex.search(query);
8014
9091
  if (scores.size === 0) {
8015
9092
  return [];
@@ -8027,24 +9104,54 @@ var Indexer = class {
8027
9104
  return results.slice(0, limit);
8028
9105
  }
8029
9106
  async getStatus() {
8030
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9107
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
8031
9108
  const failedBatchesCount = this.getFailedBatchesCount();
9109
+ const vectorCount = store.count();
9110
+ const statusReadIssues = [...readIssues];
9111
+ let startupWarning = "";
9112
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9113
+ try {
9114
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9115
+ } catch (error) {
9116
+ const message = this.getDatabaseReadIssueMessage();
9117
+ statusReadIssues.push(this.createReadIssue("database", message));
9118
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9119
+ this.recordReadIssue("database", message, error);
9120
+ }
9121
+ }
9122
+ }
9123
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9124
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9125
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
8032
9126
  return {
8033
- indexed: store.count() > 0,
8034
- vectorCount: store.count(),
9127
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9128
+ vectorCount,
8035
9129
  provider: configuredProviderInfo.provider,
8036
9130
  model: configuredProviderInfo.modelInfo.model,
8037
9131
  indexPath: this.indexPath,
8038
9132
  currentBranch: this.currentBranch,
8039
9133
  baseBranch: this.baseBranch,
8040
- compatibility: this.indexCompatibility,
9134
+ compatibility,
8041
9135
  failedBatchesCount,
8042
9136
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8043
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9137
+ warning: warning || void 0
8044
9138
  };
8045
9139
  }
9140
+ async forceIndex(onProgress) {
9141
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9142
+ await this.ensureInitializedUnlocked(recoveredOwners);
9143
+ await this.clearIndexUnlocked();
9144
+ return this.indexUnlocked(onProgress, [], true);
9145
+ });
9146
+ }
8046
9147
  async clearIndex() {
8047
- const { store, invertedIndex, database } = await this.ensureInitialized();
9148
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9149
+ await this.ensureInitializedUnlocked(recoveredOwners);
9150
+ await this.clearIndexUnlocked();
9151
+ });
9152
+ }
9153
+ async clearIndexUnlocked() {
9154
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8048
9155
  if (this.config.scope === "global") {
8049
9156
  store.load();
8050
9157
  invertedIndex.load();
@@ -8071,7 +9178,7 @@ var Indexer = class {
8071
9178
  store.clear();
8072
9179
  store.save();
8073
9180
  invertedIndex.clear();
8074
- invertedIndex.save();
9181
+ this.saveInvertedIndex(invertedIndex);
8075
9182
  this.fileHashCache.clear();
8076
9183
  this.saveFileHashCache();
8077
9184
  database.clearAllIndexedData();
@@ -8095,14 +9202,7 @@ var Indexer = class {
8095
9202
  this.indexCompatibility = compatibility;
8096
9203
  return;
8097
9204
  }
8098
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8099
- if (this.host !== "opencode") {
8100
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8101
- }
8102
- const isLocalProjectIndex = localProjectIndexPaths.some(
8103
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8104
- );
8105
- if (!isLocalProjectIndex) {
9205
+ if (!this.isLocalProjectIndexPath()) {
8106
9206
  throw new Error(
8107
9207
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8108
9208
  );
@@ -8110,7 +9210,7 @@ var Indexer = class {
8110
9210
  store.clear();
8111
9211
  store.save();
8112
9212
  invertedIndex.clear();
8113
- invertedIndex.save();
9213
+ this.saveInvertedIndex(invertedIndex);
8114
9214
  this.fileHashCache.clear();
8115
9215
  this.saveFileHashCache();
8116
9216
  database.clearAllIndexedData();
@@ -8128,7 +9228,13 @@ var Indexer = class {
8128
9228
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8129
9229
  }
8130
9230
  async healthCheck() {
8131
- const { store, invertedIndex, database } = await this.ensureInitialized();
9231
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9232
+ await this.ensureInitializedUnlocked(recoveredOwners);
9233
+ return this.healthCheckUnlocked();
9234
+ });
9235
+ }
9236
+ async healthCheckUnlocked() {
9237
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8132
9238
  this.logger.gc("info", "Starting health check");
8133
9239
  const allMetadata = store.getAllMetadata();
8134
9240
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8141,7 +9247,7 @@ var Indexer = class {
8141
9247
  const removedChunkKeys = [];
8142
9248
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8143
9249
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8144
- if (!(0, import_fs7.existsSync)(filePath)) {
9250
+ if (!(0, import_fs8.existsSync)(filePath)) {
8145
9251
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8146
9252
  for (const key of chunkKeys) {
8147
9253
  removedChunkKeys.push(key);
@@ -8166,7 +9272,7 @@ var Indexer = class {
8166
9272
  const removedCount = removedChunkKeys.length;
8167
9273
  if (removedCount > 0) {
8168
9274
  store.save();
8169
- invertedIndex.save();
9275
+ this.saveInvertedIndex(invertedIndex);
8170
9276
  }
8171
9277
  let gcOrphanEmbeddings;
8172
9278
  let gcOrphanChunks;
@@ -8181,7 +9287,7 @@ var Indexer = class {
8181
9287
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8182
9288
  throw error;
8183
9289
  }
8184
- await this.ensureInitialized();
9290
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8185
9291
  return {
8186
9292
  removed: 0,
8187
9293
  filePaths: [],
@@ -8190,7 +9296,7 @@ var Indexer = class {
8190
9296
  gcOrphanSymbols: 0,
8191
9297
  gcOrphanCallEdges: 0,
8192
9298
  resetCorruptedIndex: true,
8193
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9299
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8194
9300
  };
8195
9301
  }
8196
9302
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8203,7 +9309,13 @@ var Indexer = class {
8203
9309
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8204
9310
  }
8205
9311
  async retryFailedBatches() {
8206
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9312
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9313
+ await this.ensureInitializedUnlocked(recoveredOwners);
9314
+ return this.retryFailedBatchesUnlocked();
9315
+ });
9316
+ }
9317
+ async retryFailedBatchesUnlocked() {
9318
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8207
9319
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8208
9320
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8209
9321
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8367,7 +9479,7 @@ var Indexer = class {
8367
9479
  }
8368
9480
  if (succeeded > 0) {
8369
9481
  store.save();
8370
- invertedIndex.save();
9482
+ this.saveInvertedIndex(invertedIndex);
8371
9483
  }
8372
9484
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8373
9485
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8395,15 +9507,16 @@ var Indexer = class {
8395
9507
  }
8396
9508
  }
8397
9509
  async getDatabaseStats() {
8398
- const { database } = await this.ensureInitialized();
9510
+ const { database, readIssues } = await this.ensureInitialized();
9511
+ this.requireReadableComponents(readIssues, "database");
8399
9512
  return database.getStats();
8400
9513
  }
8401
9514
  getLogger() {
8402
9515
  return this.logger;
8403
9516
  }
8404
9517
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8405
- const { store, provider, database } = await this.ensureInitialized();
8406
- const compatibility = this.checkCompatibility();
9518
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9519
+ this.requireReadableComponents(readIssues, "vectors", "database");
8407
9520
  if (!compatibility.compatible) {
8408
9521
  throw new Error(
8409
9522
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8493,7 +9606,7 @@ var Indexer = class {
8493
9606
  let content = "";
8494
9607
  if (this.config.search.includeContext) {
8495
9608
  try {
8496
- const fileContent = await import_fs7.promises.readFile(
9609
+ const fileContent = await import_fs8.promises.readFile(
8497
9610
  r.metadata.filePath,
8498
9611
  "utf-8"
8499
9612
  );
@@ -8510,13 +9623,15 @@ var Indexer = class {
8510
9623
  content,
8511
9624
  score: r.score,
8512
9625
  chunkType: r.metadata.chunkType,
8513
- name: r.metadata.name
9626
+ name: r.metadata.name,
9627
+ blame: blameFromMetadata(r.metadata)
8514
9628
  };
8515
9629
  })
8516
9630
  );
8517
9631
  }
8518
9632
  async getCallers(targetName, callTypeFilter) {
8519
- const { database } = await this.ensureInitialized();
9633
+ const { database, readIssues } = await this.ensureInitialized();
9634
+ this.requireReadableComponents(readIssues, "database");
8520
9635
  const seen = /* @__PURE__ */ new Set();
8521
9636
  const results = [];
8522
9637
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8530,7 +9645,8 @@ var Indexer = class {
8530
9645
  return results;
8531
9646
  }
8532
9647
  async getCallees(symbolId, callTypeFilter) {
8533
- const { database } = await this.ensureInitialized();
9648
+ const { database, readIssues } = await this.ensureInitialized();
9649
+ this.requireReadableComponents(readIssues, "database");
8534
9650
  const seen = /* @__PURE__ */ new Set();
8535
9651
  const results = [];
8536
9652
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8544,44 +9660,51 @@ var Indexer = class {
8544
9660
  return results;
8545
9661
  }
8546
9662
  async findCallPath(fromName, toName, maxDepth) {
8547
- const { database } = await this.ensureInitialized();
9663
+ const { database, readIssues } = await this.ensureInitialized();
9664
+ this.requireReadableComponents(readIssues, "database");
8548
9665
  let shortest = [];
8549
9666
  for (const branchKey of this.getBranchCatalogKeys()) {
8550
- const path22 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8551
- if (path22.length > 0 && (shortest.length === 0 || path22.length < shortest.length)) {
8552
- shortest = path22;
9667
+ const path24 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9668
+ if (path24.length > 0 && (shortest.length === 0 || path24.length < shortest.length)) {
9669
+ shortest = path24;
8553
9670
  }
8554
9671
  }
8555
9672
  return shortest;
8556
9673
  }
8557
9674
  async getSymbolsForBranch(branch) {
8558
- const { database } = await this.ensureInitialized();
9675
+ const { database, readIssues } = await this.ensureInitialized();
9676
+ this.requireReadableComponents(readIssues, "database");
8559
9677
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8560
9678
  return database.getSymbolsForBranch(resolvedBranch);
8561
9679
  }
8562
9680
  async getSymbolsForFiles(filePaths, branch) {
8563
- const { database } = await this.ensureInitialized();
9681
+ const { database, readIssues } = await this.ensureInitialized();
9682
+ this.requireReadableComponents(readIssues, "database");
8564
9683
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8565
9684
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8566
9685
  }
8567
9686
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8568
- const { database } = await this.ensureInitialized();
9687
+ const { database, readIssues } = await this.ensureInitialized();
9688
+ this.requireReadableComponents(readIssues, "database");
8569
9689
  const branch = this.getBranchCatalogKey();
8570
9690
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8571
9691
  }
8572
9692
  async detectCommunities(branch, symbolIds) {
8573
- const { database } = await this.ensureInitialized();
9693
+ const { database, readIssues } = await this.ensureInitialized();
9694
+ this.requireReadableComponents(readIssues, "database");
8574
9695
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8575
9696
  return database.detectCommunities(resolvedBranch, symbolIds);
8576
9697
  }
8577
9698
  async computeCentrality(branch) {
8578
- const { database } = await this.ensureInitialized();
9699
+ const { database, readIssues } = await this.ensureInitialized();
9700
+ this.requireReadableComponents(readIssues, "database");
8579
9701
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8580
9702
  return database.computeCentrality(resolvedBranch);
8581
9703
  }
8582
9704
  async getPrImpact(opts) {
8583
- const { database } = await this.ensureInitialized();
8584
- const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
9705
+ const { database, readIssues } = await this.ensureInitialized();
9706
+ this.requireReadableComponents(readIssues, "database");
9707
+ const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8585
9708
  const changedFilesResult = await getChangedFiles({
8586
9709
  pr: opts.pr,
8587
9710
  branch: opts.branch,
@@ -8603,7 +9726,7 @@ var Indexer = class {
8603
9726
  "Run index_codebase first to build the call graph and symbol index for this branch."
8604
9727
  );
8605
9728
  }
8606
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9729
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8607
9730
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8608
9731
  const directIds = directSymbols.map((s) => s.id);
8609
9732
  const direction = opts.direction ?? "both";
@@ -8665,7 +9788,7 @@ var Indexer = class {
8665
9788
  if (opts.checkConflicts) {
8666
9789
  conflictingPRs = [];
8667
9790
  try {
8668
- const { stdout } = await execFileAsync2(
9791
+ const { stdout } = await execFileAsync3(
8669
9792
  "gh",
8670
9793
  ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8671
9794
  { cwd: this.projectRoot, timeout: 3e4 }
@@ -8686,7 +9809,7 @@ var Indexer = class {
8686
9809
  projectRoot: this.projectRoot,
8687
9810
  baseBranch: this.baseBranch
8688
9811
  });
8689
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9812
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8690
9813
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8691
9814
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8692
9815
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8736,7 +9859,8 @@ var Indexer = class {
8736
9859
  };
8737
9860
  }
8738
9861
  async getVisualizationData(options) {
8739
- const { database, store } = await this.ensureInitialized();
9862
+ const { database, store, readIssues } = await this.ensureInitialized();
9863
+ this.requireReadableComponents(readIssues, "vectors", "database");
8740
9864
  const seenSymbols = /* @__PURE__ */ new Map();
8741
9865
  const seenEdges = /* @__PURE__ */ new Map();
8742
9866
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8749,12 +9873,12 @@ var Indexer = class {
8749
9873
  if (meta.filePath) filePaths.add(meta.filePath);
8750
9874
  }
8751
9875
  const directory = options?.directory?.replace(/\/$/, "");
8752
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9876
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8753
9877
  for (const filePath of filePaths) {
8754
9878
  if (directory) {
8755
- const absoluteFilePath = path11.resolve(filePath);
9879
+ const absoluteFilePath = path13.resolve(filePath);
8756
9880
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8757
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9881
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8758
9882
  if (!matchesRelative && !matchesProjectRelative) {
8759
9883
  continue;
8760
9884
  }
@@ -8776,53 +9900,64 @@ var Indexer = class {
8776
9900
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8777
9901
  }
8778
9902
  async close() {
8779
- await this.database?.close();
9903
+ this.database?.close();
9904
+ for (const database of this.retiredDatabases) {
9905
+ database.close();
9906
+ }
9907
+ this.retiredDatabases = [];
8780
9908
  this.database = null;
8781
9909
  this.store = null;
8782
9910
  this.invertedIndex = null;
8783
9911
  this.provider = null;
8784
9912
  this.reranker = null;
9913
+ this.configuredProviderInfo = null;
9914
+ this.indexCompatibility = null;
9915
+ this.initializationMode = "none";
9916
+ this.readIssues = [];
9917
+ this.readerArtifactFingerprint = null;
9918
+ this.writerArtifactFingerprint = null;
9919
+ this.readerArtifactRetryAfter.clear();
8785
9920
  }
8786
9921
  };
8787
9922
 
8788
9923
  // src/tools/knowledge-base-paths.ts
8789
- var path12 = __toESM(require("path"), 1);
9924
+ var path14 = __toESM(require("path"), 1);
8790
9925
  function resolveConfigPathValue(value, baseDir) {
8791
9926
  const trimmed = value.trim();
8792
9927
  if (!trimmed) {
8793
9928
  return trimmed;
8794
9929
  }
8795
- const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
8796
- return path12.normalize(absolutePath);
9930
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9931
+ return path14.normalize(absolutePath);
8797
9932
  }
8798
9933
  function serializeConfigPathValue(value, baseDir) {
8799
9934
  const trimmed = value.trim();
8800
9935
  if (!trimmed) {
8801
9936
  return trimmed;
8802
9937
  }
8803
- if (!path12.isAbsolute(trimmed)) {
8804
- return normalizePathSeparators(path12.normalize(trimmed));
9938
+ if (!path14.isAbsolute(trimmed)) {
9939
+ return normalizePathSeparators(path14.normalize(trimmed));
8805
9940
  }
8806
- const relativePath = path12.relative(baseDir, trimmed);
8807
- if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
8808
- return normalizePathSeparators(path12.normalize(relativePath || "."));
9941
+ const relativePath = path14.relative(baseDir, trimmed);
9942
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9943
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8809
9944
  }
8810
- return path12.normalize(trimmed);
9945
+ return path14.normalize(trimmed);
8811
9946
  }
8812
9947
  function resolveKnowledgeBasePath(value, projectRoot) {
8813
- return path12.isAbsolute(value) ? value : path12.resolve(projectRoot, value);
9948
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
8814
9949
  }
8815
9950
  function normalizeKnowledgeBasePath2(value, projectRoot) {
8816
- return path12.normalize(resolveKnowledgeBasePath(value, projectRoot));
9951
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
8817
9952
  }
8818
9953
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
8819
- const normalizedInput = path12.normalize(inputPath);
9954
+ const normalizedInput = path14.normalize(inputPath);
8820
9955
  return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
8821
9956
  }
8822
9957
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
8823
- const normalizedInput = path12.normalize(inputPath);
9958
+ const normalizedInput = path14.normalize(inputPath);
8824
9959
  return knowledgeBases.findIndex(
8825
- (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
9960
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
8826
9961
  );
8827
9962
  }
8828
9963
 
@@ -8922,6 +10057,10 @@ function formatStatus(status) {
8922
10057
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
8923
10058
  }
8924
10059
  }
10060
+ if (status.warning) {
10061
+ lines.push("");
10062
+ lines.push(`INDEX WARNING: ${status.warning}`);
10063
+ }
8925
10064
  if (status.compatibility && !status.compatibility.compatible) {
8926
10065
  lines.push("");
8927
10066
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8974,7 +10113,7 @@ function formatCodebasePeek(results) {
8974
10113
  const formatted = results.map((r, idx) => {
8975
10114
  const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
8976
10115
  const name = r.name ? `"${r.name}"` : "(anonymous)";
8977
- return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
10116
+ return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
8978
10117
  });
8979
10118
  return formatted.join("\n");
8980
10119
  }
@@ -9006,16 +10145,57 @@ function formatHealthCheck(result) {
9006
10145
  }
9007
10146
  return lines.join("\n");
9008
10147
  }
10148
+ function formatCallGraphCallers(name, callers, relationshipType) {
10149
+ if (callers.length === 0) {
10150
+ return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
10151
+ }
10152
+ const formatted = callers.map((edge, index) => {
10153
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10154
+ return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
10155
+ });
10156
+ return `"${name}" is called by ${callers.length} function(s):
10157
+
10158
+ ${formatted.join("\n")}`;
10159
+ }
10160
+ function formatCallGraphCallees(symbolId, callees, relationshipType) {
10161
+ if (callees.length === 0) {
10162
+ return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
10163
+ }
10164
+ return callees.map((edge, index) => {
10165
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10166
+ return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
10167
+ }).join("\n");
10168
+ }
10169
+ function formatCallGraphPath(from, to, path24) {
10170
+ if (path24.length === 0) {
10171
+ return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
10172
+ }
10173
+ const formatted = path24.map((hop, index) => {
10174
+ const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
10175
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10176
+ return `${prefix} ${hop.symbolName}${location}`;
10177
+ });
10178
+ return `Path (${path24.length} hops):
10179
+ ${formatted.join("\n")}`;
10180
+ }
9009
10181
  function formatResultHeader(result, index) {
9010
10182
  return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
9011
10183
  }
10184
+ function formatBlame(result) {
10185
+ if (!result.blame) {
10186
+ return "";
10187
+ }
10188
+ const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
10189
+ return `
10190
+ ${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
10191
+ }
9012
10192
  function formatDefinitionLookup(results, query) {
9013
10193
  if (results.length === 0) {
9014
10194
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
9015
10195
  }
9016
10196
  const formatted = results.map((r, idx) => {
9017
10197
  const header = formatResultHeader(r, idx);
9018
- return `${header} (score: ${r.score.toFixed(2)})
10198
+ return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
9019
10199
  \`\`\`
9020
10200
  ${truncateContent(r.content)}
9021
10201
  \`\`\``;
@@ -9026,7 +10206,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
9026
10206
  const formatted = results.map((r, idx) => {
9027
10207
  const header = formatResultHeader(r, idx);
9028
10208
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
9029
- return `${header} ${scoreLabel}
10209
+ return `${header} ${scoreLabel}${formatBlame(r)}
9030
10210
  \`\`\`
9031
10211
  ${truncateContent(r.content)}
9032
10212
  \`\`\``;
@@ -9035,8 +10215,8 @@ ${truncateContent(r.content)}
9035
10215
  }
9036
10216
 
9037
10217
  // src/tools/config-state.ts
9038
- var import_fs8 = require("fs");
9039
- var path13 = __toESM(require("path"), 1);
10218
+ var import_fs9 = require("fs");
10219
+ var path15 = __toESM(require("path"), 1);
9040
10220
  function normalizeKnowledgeBasePaths(config, projectRoot) {
9041
10221
  const normalized = { ...config };
9042
10222
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -9063,10 +10243,10 @@ function loadEditableConfig(projectRoot, host = "opencode") {
9063
10243
  }
9064
10244
  function saveConfig(projectRoot, config, host = "opencode") {
9065
10245
  const configPath = getConfigPath(projectRoot, host);
9066
- const configDir = path13.dirname(configPath);
9067
- const configBaseDir = path13.dirname(configDir);
9068
- if (!(0, import_fs8.existsSync)(configDir)) {
9069
- (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10246
+ const configDir = path15.dirname(configPath);
10247
+ const configBaseDir = path15.dirname(configDir);
10248
+ if (!(0, import_fs9.existsSync)(configDir)) {
10249
+ (0, import_fs9.mkdirSync)(configDir, { recursive: true });
9070
10250
  }
9071
10251
  const serializableConfig = { ...config };
9072
10252
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9074,13 +10254,31 @@ function saveConfig(projectRoot, config, host = "opencode") {
9074
10254
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9075
10255
  );
9076
10256
  }
9077
- (0, import_fs8.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10257
+ (0, import_fs9.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9078
10258
  }
9079
10259
 
9080
10260
  // src/tools/operations.ts
9081
10261
  var indexerCache = /* @__PURE__ */ new Map();
9082
10262
  var configCache = /* @__PURE__ */ new Map();
9083
10263
  var defaultProjectRoots = /* @__PURE__ */ new Map();
10264
+ function getIndexBusyResult(error) {
10265
+ if (!isIndexLockContentionError(error)) return null;
10266
+ const owner = error.owner;
10267
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10268
+ if (error.reason === "legacy-lock") {
10269
+ return {
10270
+ kind: "busy",
10271
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10272
+ };
10273
+ }
10274
+ if (error.reason === "unknown-owner") {
10275
+ return {
10276
+ kind: "busy",
10277
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10278
+ };
10279
+ }
10280
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10281
+ }
9084
10282
  function getProjectRoot(projectRoot, host) {
9085
10283
  if (projectRoot) {
9086
10284
  return projectRoot;
@@ -9125,8 +10323,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
9125
10323
  }
9126
10324
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
9127
10325
  const root = getProjectRoot(projectRoot, host);
9128
- const localIndexPath = path14.join(root, getHostProjectIndexRelativePath(host));
9129
- if ((0, import_fs9.existsSync)(localIndexPath)) {
10326
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10327
+ if ((0, import_fs10.existsSync)(localIndexPath)) {
9130
10328
  return false;
9131
10329
  }
9132
10330
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
@@ -9140,7 +10338,10 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
9140
10338
  chunkType: options.chunkType,
9141
10339
  contextLines: options.contextLines,
9142
10340
  metadataOnly: options.metadataOnly,
9143
- definitionIntent: options.definitionIntent
10341
+ definitionIntent: options.definitionIntent,
10342
+ blameAuthor: options.blameAuthor,
10343
+ blameSha: options.blameSha,
10344
+ blameSince: options.blameSince
9144
10345
  });
9145
10346
  }
9146
10347
  async function findSimilarCode(projectRoot, host, code, options = {}) {
@@ -9179,35 +10380,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
9179
10380
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
9180
10381
  const root = getProjectRoot(projectRoot, host);
9181
10382
  const key = getIndexerCacheKey(root, host);
9182
- const cachedConfig = configCache.get(key);
9183
10383
  let indexer = getIndexerForProject(root, host);
9184
- if (args.estimateOnly) {
9185
- return { kind: "estimate", estimate: await indexer.estimateCost() };
9186
- }
9187
- if (args.force) {
9188
- if (shouldForceLocalizeProjectIndex(root, host)) {
9189
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
9190
- refreshIndexerForDirectory(root, host, cachedConfig);
9191
- indexer = getIndexerForProject(root, host);
9192
- }
9193
- await indexer.clearIndex();
9194
- refreshIndexerForDirectory(root, host, cachedConfig);
9195
- indexer = getIndexerForProject(root, host);
9196
- }
9197
- const stats = await indexer.index((progress) => {
9198
- if (onProgress) {
9199
- return onProgress(formatProgressTitle(progress), {
9200
- phase: progress.phase,
9201
- filesProcessed: progress.filesProcessed,
9202
- totalFiles: progress.totalFiles,
9203
- chunksProcessed: progress.chunksProcessed,
9204
- totalChunks: progress.totalChunks,
9205
- percentage: calculatePercentage(progress)
10384
+ const runtimeConfig = configCache.get(key);
10385
+ try {
10386
+ if (args.estimateOnly) {
10387
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10388
+ }
10389
+ const runIndex = async (target) => {
10390
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10391
+ return operation((progress) => {
10392
+ if (onProgress) {
10393
+ return onProgress(formatProgressTitle(progress), {
10394
+ phase: progress.phase,
10395
+ filesProcessed: progress.filesProcessed,
10396
+ totalFiles: progress.totalFiles,
10397
+ chunksProcessed: progress.chunksProcessed,
10398
+ totalChunks: progress.totalChunks,
10399
+ percentage: calculatePercentage(progress)
10400
+ });
10401
+ }
10402
+ return Promise.resolve();
9206
10403
  });
10404
+ };
10405
+ let stats;
10406
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10407
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10408
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10409
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10410
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10411
+ indexer = getIndexerForProject(root, host);
10412
+ return runIndex(indexer);
10413
+ }, { completeRecoveries: false });
10414
+ } else {
10415
+ stats = await runIndex(indexer);
9207
10416
  }
9208
- return Promise.resolve();
9209
- });
9210
- return { kind: "stats", stats };
10417
+ return { kind: "stats", stats };
10418
+ } catch (error) {
10419
+ const busyResult = getIndexBusyResult(error);
10420
+ if (!busyResult) throw error;
10421
+ return busyResult;
10422
+ }
9211
10423
  }
9212
10424
  async function getIndexStatus(projectRoot, host) {
9213
10425
  const indexer = getIndexerForProject(projectRoot, host);
@@ -9217,6 +10429,15 @@ async function getIndexHealthCheck(projectRoot, host) {
9217
10429
  const indexer = getIndexerForProject(projectRoot, host);
9218
10430
  return indexer.healthCheck();
9219
10431
  }
10432
+ async function runIndexHealthCheck(projectRoot, host) {
10433
+ try {
10434
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
10435
+ } catch (error) {
10436
+ const busyResult = getIndexBusyResult(error);
10437
+ if (!busyResult) throw error;
10438
+ return busyResult;
10439
+ }
10440
+ }
9220
10441
  async function getIndexMetrics(projectRoot, host) {
9221
10442
  const indexer = getIndexerForProject(projectRoot, host);
9222
10443
  const logger = indexer.getLogger();
@@ -9272,15 +10493,15 @@ async function getIndexLogs(projectRoot, host, args) {
9272
10493
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9273
10494
  const root = getProjectRoot(projectRoot, host);
9274
10495
  const inputPath = knowledgeBasePath.trim();
9275
- const normalizedPath = path14.resolve(
9276
- path14.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10496
+ const normalizedPath = path16.resolve(
10497
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9277
10498
  );
9278
- if (!(0, import_fs9.existsSync)(normalizedPath)) {
10499
+ if (!(0, import_fs10.existsSync)(normalizedPath)) {
9279
10500
  return `Error: Directory does not exist: ${normalizedPath}`;
9280
10501
  }
9281
10502
  let realPath;
9282
10503
  try {
9283
- realPath = (0, import_fs9.realpathSync)(normalizedPath);
10504
+ realPath = (0, import_fs10.realpathSync)(normalizedPath);
9284
10505
  } catch {
9285
10506
  return `Error: Cannot resolve path: ${normalizedPath}`;
9286
10507
  }
@@ -9309,13 +10530,13 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
9309
10530
  }
9310
10531
  }
9311
10532
  for (const dotDir of sensitiveDotDirs) {
9312
- const sensitiveDir = path14.join(homeDir, dotDir);
10533
+ const sensitiveDir = path16.join(homeDir, dotDir);
9313
10534
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9314
10535
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9315
10536
  }
9316
10537
  }
9317
10538
  try {
9318
- const stat4 = (0, import_fs9.statSync)(normalizedPath);
10539
+ const stat4 = (0, import_fs10.statSync)(normalizedPath);
9319
10540
  if (!stat4.isDirectory()) {
9320
10541
  return `Error: Path is not a directory: ${normalizedPath}`;
9321
10542
  }
@@ -9355,7 +10576,7 @@ function listKnowledgeBases(projectRoot, host) {
9355
10576
  for (let i = 0; i < knowledgeBases.length; i++) {
9356
10577
  const kb = knowledgeBases[i];
9357
10578
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9358
- const exists = (0, import_fs9.existsSync)(resolvedPath);
10579
+ const exists = (0, import_fs10.existsSync)(resolvedPath);
9359
10580
  result += `[${i + 1}] ${kb}
9360
10581
  `;
9361
10582
  result += ` Resolved: ${resolvedPath}
@@ -9364,7 +10585,7 @@ function listKnowledgeBases(projectRoot, host) {
9364
10585
  `;
9365
10586
  if (exists) {
9366
10587
  try {
9367
- const stat4 = (0, import_fs9.statSync)(resolvedPath);
10588
+ const stat4 = (0, import_fs10.statSync)(resolvedPath);
9368
10589
  result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
9369
10590
  `;
9370
10591
  } catch {
@@ -9372,7 +10593,7 @@ function listKnowledgeBases(projectRoot, host) {
9372
10593
  }
9373
10594
  result += "\n";
9374
10595
  }
9375
- const hasHostConfig = (0, import_fs9.existsSync)(path14.join(root, getHostProjectConfigRelativePath(host)));
10596
+ const hasHostConfig = (0, import_fs10.existsSync)(path16.join(root, getHostProjectConfigRelativePath(host)));
9376
10597
  if (hasHostConfig) {
9377
10598
  result += `
9378
10599
  Config sources: 1 file(s).`;
@@ -9495,7 +10716,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9495
10716
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
9496
10717
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
9497
10718
  if (wantBigintFsStats) {
9498
- this._stat = (path22) => statMethod(path22, { bigint: true });
10719
+ this._stat = (path24) => statMethod(path24, { bigint: true });
9499
10720
  } else {
9500
10721
  this._stat = statMethod;
9501
10722
  }
@@ -9520,8 +10741,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9520
10741
  const par = this.parent;
9521
10742
  const fil = par && par.files;
9522
10743
  if (fil && fil.length > 0) {
9523
- const { path: path22, depth } = par;
9524
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path22));
10744
+ const { path: path24, depth } = par;
10745
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
9525
10746
  const awaited = await Promise.all(slice);
9526
10747
  for (const entry of awaited) {
9527
10748
  if (!entry)
@@ -9561,20 +10782,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
9561
10782
  this.reading = false;
9562
10783
  }
9563
10784
  }
9564
- async _exploreDir(path22, depth) {
10785
+ async _exploreDir(path24, depth) {
9565
10786
  let files;
9566
10787
  try {
9567
- files = await (0, import_promises.readdir)(path22, this._rdOptions);
10788
+ files = await (0, import_promises.readdir)(path24, this._rdOptions);
9568
10789
  } catch (error) {
9569
10790
  this._onError(error);
9570
10791
  }
9571
- return { files, depth, path: path22 };
10792
+ return { files, depth, path: path24 };
9572
10793
  }
9573
- async _formatEntry(dirent, path22) {
10794
+ async _formatEntry(dirent, path24) {
9574
10795
  let entry;
9575
10796
  const basename5 = this._isDirent ? dirent.name : dirent;
9576
10797
  try {
9577
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path22, basename5));
10798
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path24, basename5));
9578
10799
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
9579
10800
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
9580
10801
  } catch (err) {
@@ -9974,16 +11195,16 @@ var delFromSet = (main, prop, item) => {
9974
11195
  };
9975
11196
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
9976
11197
  var FsWatchInstances = /* @__PURE__ */ new Map();
9977
- function createFsWatchInstance(path22, options, listener, errHandler, emitRaw) {
11198
+ function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
9978
11199
  const handleEvent = (rawEvent, evPath) => {
9979
- listener(path22);
9980
- emitRaw(rawEvent, evPath, { watchedPath: path22 });
9981
- if (evPath && path22 !== evPath) {
9982
- fsWatchBroadcast(sp.resolve(path22, evPath), KEY_LISTENERS, sp.join(path22, evPath));
11200
+ listener(path24);
11201
+ emitRaw(rawEvent, evPath, { watchedPath: path24 });
11202
+ if (evPath && path24 !== evPath) {
11203
+ fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
9983
11204
  }
9984
11205
  };
9985
11206
  try {
9986
- return (0, import_node_fs.watch)(path22, {
11207
+ return (0, import_node_fs.watch)(path24, {
9987
11208
  persistent: options.persistent
9988
11209
  }, handleEvent);
9989
11210
  } catch (error) {
@@ -9999,12 +11220,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
9999
11220
  listener(val1, val2, val3);
10000
11221
  });
10001
11222
  };
10002
- var setFsWatchListener = (path22, fullPath, options, handlers) => {
11223
+ var setFsWatchListener = (path24, fullPath, options, handlers) => {
10003
11224
  const { listener, errHandler, rawEmitter } = handlers;
10004
11225
  let cont = FsWatchInstances.get(fullPath);
10005
11226
  let watcher;
10006
11227
  if (!options.persistent) {
10007
- watcher = createFsWatchInstance(path22, options, listener, errHandler, rawEmitter);
11228
+ watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
10008
11229
  if (!watcher)
10009
11230
  return;
10010
11231
  return watcher.close.bind(watcher);
@@ -10015,7 +11236,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10015
11236
  addAndConvert(cont, KEY_RAW, rawEmitter);
10016
11237
  } else {
10017
11238
  watcher = createFsWatchInstance(
10018
- path22,
11239
+ path24,
10019
11240
  options,
10020
11241
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
10021
11242
  errHandler,
@@ -10030,7 +11251,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10030
11251
  cont.watcherUnusable = true;
10031
11252
  if (isWindows && error.code === "EPERM") {
10032
11253
  try {
10033
- const fd = await (0, import_promises2.open)(path22, "r");
11254
+ const fd = await (0, import_promises2.open)(path24, "r");
10034
11255
  await fd.close();
10035
11256
  broadcastErr(error);
10036
11257
  } catch (err) {
@@ -10061,7 +11282,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
10061
11282
  };
10062
11283
  };
10063
11284
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
10064
- var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
11285
+ var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
10065
11286
  const { listener, rawEmitter } = handlers;
10066
11287
  let cont = FsWatchFileInstances.get(fullPath);
10067
11288
  const copts = cont && cont.options;
@@ -10083,7 +11304,7 @@ var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
10083
11304
  });
10084
11305
  const currmtime = curr.mtimeMs;
10085
11306
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
10086
- foreach(cont.listeners, (listener2) => listener2(path22, curr));
11307
+ foreach(cont.listeners, (listener2) => listener2(path24, curr));
10087
11308
  }
10088
11309
  })
10089
11310
  };
@@ -10113,13 +11334,13 @@ var NodeFsHandler = class {
10113
11334
  * @param listener on fs change
10114
11335
  * @returns closer for the watcher instance
10115
11336
  */
10116
- _watchWithNodeFs(path22, listener) {
11337
+ _watchWithNodeFs(path24, listener) {
10117
11338
  const opts = this.fsw.options;
10118
- const directory = sp.dirname(path22);
10119
- const basename5 = sp.basename(path22);
11339
+ const directory = sp.dirname(path24);
11340
+ const basename5 = sp.basename(path24);
10120
11341
  const parent = this.fsw._getWatchedDir(directory);
10121
11342
  parent.add(basename5);
10122
- const absolutePath = sp.resolve(path22);
11343
+ const absolutePath = sp.resolve(path24);
10123
11344
  const options = {
10124
11345
  persistent: opts.persistent
10125
11346
  };
@@ -10129,12 +11350,12 @@ var NodeFsHandler = class {
10129
11350
  if (opts.usePolling) {
10130
11351
  const enableBin = opts.interval !== opts.binaryInterval;
10131
11352
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
10132
- closer = setFsWatchFileListener(path22, absolutePath, options, {
11353
+ closer = setFsWatchFileListener(path24, absolutePath, options, {
10133
11354
  listener,
10134
11355
  rawEmitter: this.fsw._emitRaw
10135
11356
  });
10136
11357
  } else {
10137
- closer = setFsWatchListener(path22, absolutePath, options, {
11358
+ closer = setFsWatchListener(path24, absolutePath, options, {
10138
11359
  listener,
10139
11360
  errHandler: this._boundHandleError,
10140
11361
  rawEmitter: this.fsw._emitRaw
@@ -10156,7 +11377,7 @@ var NodeFsHandler = class {
10156
11377
  let prevStats = stats;
10157
11378
  if (parent.has(basename5))
10158
11379
  return;
10159
- const listener = async (path22, newStats) => {
11380
+ const listener = async (path24, newStats) => {
10160
11381
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
10161
11382
  return;
10162
11383
  if (!newStats || newStats.mtimeMs === 0) {
@@ -10170,11 +11391,11 @@ var NodeFsHandler = class {
10170
11391
  this.fsw._emit(EV.CHANGE, file, newStats2);
10171
11392
  }
10172
11393
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
10173
- this.fsw._closeFile(path22);
11394
+ this.fsw._closeFile(path24);
10174
11395
  prevStats = newStats2;
10175
11396
  const closer2 = this._watchWithNodeFs(file, listener);
10176
11397
  if (closer2)
10177
- this.fsw._addPathCloser(path22, closer2);
11398
+ this.fsw._addPathCloser(path24, closer2);
10178
11399
  } else {
10179
11400
  prevStats = newStats2;
10180
11401
  }
@@ -10206,7 +11427,7 @@ var NodeFsHandler = class {
10206
11427
  * @param item basename of this item
10207
11428
  * @returns true if no more processing is needed for this entry.
10208
11429
  */
10209
- async _handleSymlink(entry, directory, path22, item) {
11430
+ async _handleSymlink(entry, directory, path24, item) {
10210
11431
  if (this.fsw.closed) {
10211
11432
  return;
10212
11433
  }
@@ -10216,7 +11437,7 @@ var NodeFsHandler = class {
10216
11437
  this.fsw._incrReadyCount();
10217
11438
  let linkPath;
10218
11439
  try {
10219
- linkPath = await (0, import_promises2.realpath)(path22);
11440
+ linkPath = await (0, import_promises2.realpath)(path24);
10220
11441
  } catch (e) {
10221
11442
  this.fsw._emitReady();
10222
11443
  return true;
@@ -10226,12 +11447,12 @@ var NodeFsHandler = class {
10226
11447
  if (dir.has(item)) {
10227
11448
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
10228
11449
  this.fsw._symlinkPaths.set(full, linkPath);
10229
- this.fsw._emit(EV.CHANGE, path22, entry.stats);
11450
+ this.fsw._emit(EV.CHANGE, path24, entry.stats);
10230
11451
  }
10231
11452
  } else {
10232
11453
  dir.add(item);
10233
11454
  this.fsw._symlinkPaths.set(full, linkPath);
10234
- this.fsw._emit(EV.ADD, path22, entry.stats);
11455
+ this.fsw._emit(EV.ADD, path24, entry.stats);
10235
11456
  }
10236
11457
  this.fsw._emitReady();
10237
11458
  return true;
@@ -10261,9 +11482,9 @@ var NodeFsHandler = class {
10261
11482
  return;
10262
11483
  }
10263
11484
  const item = entry.path;
10264
- let path22 = sp.join(directory, item);
11485
+ let path24 = sp.join(directory, item);
10265
11486
  current.add(item);
10266
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path22, item)) {
11487
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
10267
11488
  return;
10268
11489
  }
10269
11490
  if (this.fsw.closed) {
@@ -10272,8 +11493,8 @@ var NodeFsHandler = class {
10272
11493
  }
10273
11494
  if (item === target || !target && !previous.has(item)) {
10274
11495
  this.fsw._incrReadyCount();
10275
- path22 = sp.join(dir, sp.relative(dir, path22));
10276
- this._addToNodeFs(path22, initialAdd, wh, depth + 1);
11496
+ path24 = sp.join(dir, sp.relative(dir, path24));
11497
+ this._addToNodeFs(path24, initialAdd, wh, depth + 1);
10277
11498
  }
10278
11499
  }).on(EV.ERROR, this._boundHandleError);
10279
11500
  return new Promise((resolve13, reject) => {
@@ -10342,13 +11563,13 @@ var NodeFsHandler = class {
10342
11563
  * @param depth Child path actually targeted for watch
10343
11564
  * @param target Child path actually targeted for watch
10344
11565
  */
10345
- async _addToNodeFs(path22, initialAdd, priorWh, depth, target) {
11566
+ async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
10346
11567
  const ready = this.fsw._emitReady;
10347
- if (this.fsw._isIgnored(path22) || this.fsw.closed) {
11568
+ if (this.fsw._isIgnored(path24) || this.fsw.closed) {
10348
11569
  ready();
10349
11570
  return false;
10350
11571
  }
10351
- const wh = this.fsw._getWatchHelpers(path22);
11572
+ const wh = this.fsw._getWatchHelpers(path24);
10352
11573
  if (priorWh) {
10353
11574
  wh.filterPath = (entry) => priorWh.filterPath(entry);
10354
11575
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -10364,8 +11585,8 @@ var NodeFsHandler = class {
10364
11585
  const follow = this.fsw.options.followSymlinks;
10365
11586
  let closer;
10366
11587
  if (stats.isDirectory()) {
10367
- const absPath = sp.resolve(path22);
10368
- const targetPath = follow ? await (0, import_promises2.realpath)(path22) : path22;
11588
+ const absPath = sp.resolve(path24);
11589
+ const targetPath = follow ? await (0, import_promises2.realpath)(path24) : path24;
10369
11590
  if (this.fsw.closed)
10370
11591
  return;
10371
11592
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -10375,29 +11596,29 @@ var NodeFsHandler = class {
10375
11596
  this.fsw._symlinkPaths.set(absPath, targetPath);
10376
11597
  }
10377
11598
  } else if (stats.isSymbolicLink()) {
10378
- const targetPath = follow ? await (0, import_promises2.realpath)(path22) : path22;
11599
+ const targetPath = follow ? await (0, import_promises2.realpath)(path24) : path24;
10379
11600
  if (this.fsw.closed)
10380
11601
  return;
10381
11602
  const parent = sp.dirname(wh.watchPath);
10382
11603
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
10383
11604
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
10384
- closer = await this._handleDir(parent, stats, initialAdd, depth, path22, wh, targetPath);
11605
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
10385
11606
  if (this.fsw.closed)
10386
11607
  return;
10387
11608
  if (targetPath !== void 0) {
10388
- this.fsw._symlinkPaths.set(sp.resolve(path22), targetPath);
11609
+ this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
10389
11610
  }
10390
11611
  } else {
10391
11612
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
10392
11613
  }
10393
11614
  ready();
10394
11615
  if (closer)
10395
- this.fsw._addPathCloser(path22, closer);
11616
+ this.fsw._addPathCloser(path24, closer);
10396
11617
  return false;
10397
11618
  } catch (error) {
10398
11619
  if (this.fsw._handleError(error)) {
10399
11620
  ready();
10400
- return path22;
11621
+ return path24;
10401
11622
  }
10402
11623
  }
10403
11624
  }
@@ -10429,35 +11650,35 @@ function createPattern(matcher) {
10429
11650
  if (matcher.path === string)
10430
11651
  return true;
10431
11652
  if (matcher.recursive) {
10432
- const relative10 = sp2.relative(matcher.path, string);
10433
- if (!relative10) {
11653
+ const relative11 = sp2.relative(matcher.path, string);
11654
+ if (!relative11) {
10434
11655
  return false;
10435
11656
  }
10436
- return !relative10.startsWith("..") && !sp2.isAbsolute(relative10);
11657
+ return !relative11.startsWith("..") && !sp2.isAbsolute(relative11);
10437
11658
  }
10438
11659
  return false;
10439
11660
  };
10440
11661
  }
10441
11662
  return () => false;
10442
11663
  }
10443
- function normalizePath(path22) {
10444
- if (typeof path22 !== "string")
11664
+ function normalizePath(path24) {
11665
+ if (typeof path24 !== "string")
10445
11666
  throw new Error("string expected");
10446
- path22 = sp2.normalize(path22);
10447
- path22 = path22.replace(/\\/g, "/");
11667
+ path24 = sp2.normalize(path24);
11668
+ path24 = path24.replace(/\\/g, "/");
10448
11669
  let prepend = false;
10449
- if (path22.startsWith("//"))
11670
+ if (path24.startsWith("//"))
10450
11671
  prepend = true;
10451
- path22 = path22.replace(DOUBLE_SLASH_RE, "/");
11672
+ path24 = path24.replace(DOUBLE_SLASH_RE, "/");
10452
11673
  if (prepend)
10453
- path22 = "/" + path22;
10454
- return path22;
11674
+ path24 = "/" + path24;
11675
+ return path24;
10455
11676
  }
10456
11677
  function matchPatterns(patterns, testString, stats) {
10457
- const path22 = normalizePath(testString);
11678
+ const path24 = normalizePath(testString);
10458
11679
  for (let index = 0; index < patterns.length; index++) {
10459
11680
  const pattern = patterns[index];
10460
- if (pattern(path22, stats)) {
11681
+ if (pattern(path24, stats)) {
10461
11682
  return true;
10462
11683
  }
10463
11684
  }
@@ -10495,19 +11716,19 @@ var toUnix = (string) => {
10495
11716
  }
10496
11717
  return str;
10497
11718
  };
10498
- var normalizePathToUnix = (path22) => toUnix(sp2.normalize(toUnix(path22)));
10499
- var normalizeIgnored = (cwd = "") => (path22) => {
10500
- if (typeof path22 === "string") {
10501
- return normalizePathToUnix(sp2.isAbsolute(path22) ? path22 : sp2.join(cwd, path22));
11719
+ var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
11720
+ var normalizeIgnored = (cwd = "") => (path24) => {
11721
+ if (typeof path24 === "string") {
11722
+ return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
10502
11723
  } else {
10503
- return path22;
11724
+ return path24;
10504
11725
  }
10505
11726
  };
10506
- var getAbsolutePath = (path22, cwd) => {
10507
- if (sp2.isAbsolute(path22)) {
10508
- return path22;
11727
+ var getAbsolutePath = (path24, cwd) => {
11728
+ if (sp2.isAbsolute(path24)) {
11729
+ return path24;
10509
11730
  }
10510
- return sp2.join(cwd, path22);
11731
+ return sp2.join(cwd, path24);
10511
11732
  };
10512
11733
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
10513
11734
  var DirEntry = class {
@@ -10572,10 +11793,10 @@ var WatchHelper = class {
10572
11793
  dirParts;
10573
11794
  followSymlinks;
10574
11795
  statMethod;
10575
- constructor(path22, follow, fsw) {
11796
+ constructor(path24, follow, fsw) {
10576
11797
  this.fsw = fsw;
10577
- const watchPath = path22;
10578
- this.path = path22 = path22.replace(REPLACER_RE, "");
11798
+ const watchPath = path24;
11799
+ this.path = path24 = path24.replace(REPLACER_RE, "");
10579
11800
  this.watchPath = watchPath;
10580
11801
  this.fullWatchPath = sp2.resolve(watchPath);
10581
11802
  this.dirParts = [];
@@ -10715,20 +11936,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10715
11936
  this._closePromise = void 0;
10716
11937
  let paths = unifyPaths(paths_);
10717
11938
  if (cwd) {
10718
- paths = paths.map((path22) => {
10719
- const absPath = getAbsolutePath(path22, cwd);
11939
+ paths = paths.map((path24) => {
11940
+ const absPath = getAbsolutePath(path24, cwd);
10720
11941
  return absPath;
10721
11942
  });
10722
11943
  }
10723
- paths.forEach((path22) => {
10724
- this._removeIgnoredPath(path22);
11944
+ paths.forEach((path24) => {
11945
+ this._removeIgnoredPath(path24);
10725
11946
  });
10726
11947
  this._userIgnored = void 0;
10727
11948
  if (!this._readyCount)
10728
11949
  this._readyCount = 0;
10729
11950
  this._readyCount += paths.length;
10730
- Promise.all(paths.map(async (path22) => {
10731
- const res = await this._nodeFsHandler._addToNodeFs(path22, !_internal, void 0, 0, _origAdd);
11951
+ Promise.all(paths.map(async (path24) => {
11952
+ const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
10732
11953
  if (res)
10733
11954
  this._emitReady();
10734
11955
  return res;
@@ -10750,17 +11971,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10750
11971
  return this;
10751
11972
  const paths = unifyPaths(paths_);
10752
11973
  const { cwd } = this.options;
10753
- paths.forEach((path22) => {
10754
- if (!sp2.isAbsolute(path22) && !this._closers.has(path22)) {
11974
+ paths.forEach((path24) => {
11975
+ if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
10755
11976
  if (cwd)
10756
- path22 = sp2.join(cwd, path22);
10757
- path22 = sp2.resolve(path22);
11977
+ path24 = sp2.join(cwd, path24);
11978
+ path24 = sp2.resolve(path24);
10758
11979
  }
10759
- this._closePath(path22);
10760
- this._addIgnoredPath(path22);
10761
- if (this._watched.has(path22)) {
11980
+ this._closePath(path24);
11981
+ this._addIgnoredPath(path24);
11982
+ if (this._watched.has(path24)) {
10762
11983
  this._addIgnoredPath({
10763
- path: path22,
11984
+ path: path24,
10764
11985
  recursive: true
10765
11986
  });
10766
11987
  }
@@ -10824,38 +12045,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10824
12045
  * @param stats arguments to be passed with event
10825
12046
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
10826
12047
  */
10827
- async _emit(event, path22, stats) {
12048
+ async _emit(event, path24, stats) {
10828
12049
  if (this.closed)
10829
12050
  return;
10830
12051
  const opts = this.options;
10831
12052
  if (isWindows)
10832
- path22 = sp2.normalize(path22);
12053
+ path24 = sp2.normalize(path24);
10833
12054
  if (opts.cwd)
10834
- path22 = sp2.relative(opts.cwd, path22);
10835
- const args = [path22];
12055
+ path24 = sp2.relative(opts.cwd, path24);
12056
+ const args = [path24];
10836
12057
  if (stats != null)
10837
12058
  args.push(stats);
10838
12059
  const awf = opts.awaitWriteFinish;
10839
12060
  let pw;
10840
- if (awf && (pw = this._pendingWrites.get(path22))) {
12061
+ if (awf && (pw = this._pendingWrites.get(path24))) {
10841
12062
  pw.lastChange = /* @__PURE__ */ new Date();
10842
12063
  return this;
10843
12064
  }
10844
12065
  if (opts.atomic) {
10845
12066
  if (event === EVENTS.UNLINK) {
10846
- this._pendingUnlinks.set(path22, [event, ...args]);
12067
+ this._pendingUnlinks.set(path24, [event, ...args]);
10847
12068
  setTimeout(() => {
10848
- this._pendingUnlinks.forEach((entry, path23) => {
12069
+ this._pendingUnlinks.forEach((entry, path25) => {
10849
12070
  this.emit(...entry);
10850
12071
  this.emit(EVENTS.ALL, ...entry);
10851
- this._pendingUnlinks.delete(path23);
12072
+ this._pendingUnlinks.delete(path25);
10852
12073
  });
10853
12074
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
10854
12075
  return this;
10855
12076
  }
10856
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path22)) {
12077
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
10857
12078
  event = EVENTS.CHANGE;
10858
- this._pendingUnlinks.delete(path22);
12079
+ this._pendingUnlinks.delete(path24);
10859
12080
  }
10860
12081
  }
10861
12082
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -10873,16 +12094,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10873
12094
  this.emitWithAll(event, args);
10874
12095
  }
10875
12096
  };
10876
- this._awaitWriteFinish(path22, awf.stabilityThreshold, event, awfEmit);
12097
+ this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
10877
12098
  return this;
10878
12099
  }
10879
12100
  if (event === EVENTS.CHANGE) {
10880
- const isThrottled = !this._throttle(EVENTS.CHANGE, path22, 50);
12101
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
10881
12102
  if (isThrottled)
10882
12103
  return this;
10883
12104
  }
10884
12105
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
10885
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path22) : path22;
12106
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
10886
12107
  let stats2;
10887
12108
  try {
10888
12109
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -10913,23 +12134,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10913
12134
  * @param timeout duration of time to suppress duplicate actions
10914
12135
  * @returns tracking object or false if action should be suppressed
10915
12136
  */
10916
- _throttle(actionType, path22, timeout) {
12137
+ _throttle(actionType, path24, timeout) {
10917
12138
  if (!this._throttled.has(actionType)) {
10918
12139
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
10919
12140
  }
10920
12141
  const action = this._throttled.get(actionType);
10921
12142
  if (!action)
10922
12143
  throw new Error("invalid throttle");
10923
- const actionPath = action.get(path22);
12144
+ const actionPath = action.get(path24);
10924
12145
  if (actionPath) {
10925
12146
  actionPath.count++;
10926
12147
  return false;
10927
12148
  }
10928
12149
  let timeoutObject;
10929
12150
  const clear = () => {
10930
- const item = action.get(path22);
12151
+ const item = action.get(path24);
10931
12152
  const count = item ? item.count : 0;
10932
- action.delete(path22);
12153
+ action.delete(path24);
10933
12154
  clearTimeout(timeoutObject);
10934
12155
  if (item)
10935
12156
  clearTimeout(item.timeoutObject);
@@ -10937,7 +12158,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10937
12158
  };
10938
12159
  timeoutObject = setTimeout(clear, timeout);
10939
12160
  const thr = { timeoutObject, clear, count: 0 };
10940
- action.set(path22, thr);
12161
+ action.set(path24, thr);
10941
12162
  return thr;
10942
12163
  }
10943
12164
  _incrReadyCount() {
@@ -10951,44 +12172,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10951
12172
  * @param event
10952
12173
  * @param awfEmit Callback to be called when ready for event to be emitted.
10953
12174
  */
10954
- _awaitWriteFinish(path22, threshold, event, awfEmit) {
12175
+ _awaitWriteFinish(path24, threshold, event, awfEmit) {
10955
12176
  const awf = this.options.awaitWriteFinish;
10956
12177
  if (typeof awf !== "object")
10957
12178
  return;
10958
12179
  const pollInterval = awf.pollInterval;
10959
12180
  let timeoutHandler;
10960
- let fullPath = path22;
10961
- if (this.options.cwd && !sp2.isAbsolute(path22)) {
10962
- fullPath = sp2.join(this.options.cwd, path22);
12181
+ let fullPath = path24;
12182
+ if (this.options.cwd && !sp2.isAbsolute(path24)) {
12183
+ fullPath = sp2.join(this.options.cwd, path24);
10963
12184
  }
10964
12185
  const now = /* @__PURE__ */ new Date();
10965
12186
  const writes = this._pendingWrites;
10966
12187
  function awaitWriteFinishFn(prevStat) {
10967
12188
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
10968
- if (err || !writes.has(path22)) {
12189
+ if (err || !writes.has(path24)) {
10969
12190
  if (err && err.code !== "ENOENT")
10970
12191
  awfEmit(err);
10971
12192
  return;
10972
12193
  }
10973
12194
  const now2 = Number(/* @__PURE__ */ new Date());
10974
12195
  if (prevStat && curStat.size !== prevStat.size) {
10975
- writes.get(path22).lastChange = now2;
12196
+ writes.get(path24).lastChange = now2;
10976
12197
  }
10977
- const pw = writes.get(path22);
12198
+ const pw = writes.get(path24);
10978
12199
  const df = now2 - pw.lastChange;
10979
12200
  if (df >= threshold) {
10980
- writes.delete(path22);
12201
+ writes.delete(path24);
10981
12202
  awfEmit(void 0, curStat);
10982
12203
  } else {
10983
12204
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
10984
12205
  }
10985
12206
  });
10986
12207
  }
10987
- if (!writes.has(path22)) {
10988
- writes.set(path22, {
12208
+ if (!writes.has(path24)) {
12209
+ writes.set(path24, {
10989
12210
  lastChange: now,
10990
12211
  cancelWait: () => {
10991
- writes.delete(path22);
12212
+ writes.delete(path24);
10992
12213
  clearTimeout(timeoutHandler);
10993
12214
  return event;
10994
12215
  }
@@ -10999,8 +12220,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
10999
12220
  /**
11000
12221
  * Determines whether user has asked to ignore this path.
11001
12222
  */
11002
- _isIgnored(path22, stats) {
11003
- if (this.options.atomic && DOT_RE.test(path22))
12223
+ _isIgnored(path24, stats) {
12224
+ if (this.options.atomic && DOT_RE.test(path24))
11004
12225
  return true;
11005
12226
  if (!this._userIgnored) {
11006
12227
  const { cwd } = this.options;
@@ -11010,17 +12231,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11010
12231
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
11011
12232
  this._userIgnored = anymatch(list, void 0);
11012
12233
  }
11013
- return this._userIgnored(path22, stats);
12234
+ return this._userIgnored(path24, stats);
11014
12235
  }
11015
- _isntIgnored(path22, stat4) {
11016
- return !this._isIgnored(path22, stat4);
12236
+ _isntIgnored(path24, stat4) {
12237
+ return !this._isIgnored(path24, stat4);
11017
12238
  }
11018
12239
  /**
11019
12240
  * Provides a set of common helpers and properties relating to symlink handling.
11020
12241
  * @param path file or directory pattern being watched
11021
12242
  */
11022
- _getWatchHelpers(path22) {
11023
- return new WatchHelper(path22, this.options.followSymlinks, this);
12243
+ _getWatchHelpers(path24) {
12244
+ return new WatchHelper(path24, this.options.followSymlinks, this);
11024
12245
  }
11025
12246
  // Directory helpers
11026
12247
  // -----------------
@@ -11052,63 +12273,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
11052
12273
  * @param item base path of item/directory
11053
12274
  */
11054
12275
  _remove(directory, item, isDirectory) {
11055
- const path22 = sp2.join(directory, item);
11056
- const fullPath = sp2.resolve(path22);
11057
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path22) || this._watched.has(fullPath);
11058
- if (!this._throttle("remove", path22, 100))
12276
+ const path24 = sp2.join(directory, item);
12277
+ const fullPath = sp2.resolve(path24);
12278
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
12279
+ if (!this._throttle("remove", path24, 100))
11059
12280
  return;
11060
12281
  if (!isDirectory && this._watched.size === 1) {
11061
12282
  this.add(directory, item, true);
11062
12283
  }
11063
- const wp = this._getWatchedDir(path22);
12284
+ const wp = this._getWatchedDir(path24);
11064
12285
  const nestedDirectoryChildren = wp.getChildren();
11065
- nestedDirectoryChildren.forEach((nested) => this._remove(path22, nested));
12286
+ nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
11066
12287
  const parent = this._getWatchedDir(directory);
11067
12288
  const wasTracked = parent.has(item);
11068
12289
  parent.remove(item);
11069
12290
  if (this._symlinkPaths.has(fullPath)) {
11070
12291
  this._symlinkPaths.delete(fullPath);
11071
12292
  }
11072
- let relPath = path22;
12293
+ let relPath = path24;
11073
12294
  if (this.options.cwd)
11074
- relPath = sp2.relative(this.options.cwd, path22);
12295
+ relPath = sp2.relative(this.options.cwd, path24);
11075
12296
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
11076
12297
  const event = this._pendingWrites.get(relPath).cancelWait();
11077
12298
  if (event === EVENTS.ADD)
11078
12299
  return;
11079
12300
  }
11080
- this._watched.delete(path22);
12301
+ this._watched.delete(path24);
11081
12302
  this._watched.delete(fullPath);
11082
12303
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
11083
- if (wasTracked && !this._isIgnored(path22))
11084
- this._emit(eventName, path22);
11085
- this._closePath(path22);
12304
+ if (wasTracked && !this._isIgnored(path24))
12305
+ this._emit(eventName, path24);
12306
+ this._closePath(path24);
11086
12307
  }
11087
12308
  /**
11088
12309
  * Closes all watchers for a path
11089
12310
  */
11090
- _closePath(path22) {
11091
- this._closeFile(path22);
11092
- const dir = sp2.dirname(path22);
11093
- this._getWatchedDir(dir).remove(sp2.basename(path22));
12311
+ _closePath(path24) {
12312
+ this._closeFile(path24);
12313
+ const dir = sp2.dirname(path24);
12314
+ this._getWatchedDir(dir).remove(sp2.basename(path24));
11094
12315
  }
11095
12316
  /**
11096
12317
  * Closes only file-specific watchers
11097
12318
  */
11098
- _closeFile(path22) {
11099
- const closers = this._closers.get(path22);
12319
+ _closeFile(path24) {
12320
+ const closers = this._closers.get(path24);
11100
12321
  if (!closers)
11101
12322
  return;
11102
12323
  closers.forEach((closer) => closer());
11103
- this._closers.delete(path22);
12324
+ this._closers.delete(path24);
11104
12325
  }
11105
- _addPathCloser(path22, closer) {
12326
+ _addPathCloser(path24, closer) {
11106
12327
  if (!closer)
11107
12328
  return;
11108
- let list = this._closers.get(path22);
12329
+ let list = this._closers.get(path24);
11109
12330
  if (!list) {
11110
12331
  list = [];
11111
- this._closers.set(path22, list);
12332
+ this._closers.set(path24, list);
11112
12333
  }
11113
12334
  list.push(closer);
11114
12335
  }
@@ -11138,7 +12359,7 @@ function watch(paths, options = {}) {
11138
12359
  var chokidar_default = { watch, FSWatcher };
11139
12360
 
11140
12361
  // src/watcher/file-watcher.ts
11141
- var path15 = __toESM(require("path"), 1);
12362
+ var path17 = __toESM(require("path"), 1);
11142
12363
  var FileWatcher = class {
11143
12364
  watcher = null;
11144
12365
  projectRoot;
@@ -11149,6 +12370,8 @@ var FileWatcher = class {
11149
12370
  debounceTimer = null;
11150
12371
  debounceMs = 1e3;
11151
12372
  onChanges = null;
12373
+ readyPromise = null;
12374
+ resolveReady = null;
11152
12375
  constructor(projectRoot, config, host = "opencode", options = {}) {
11153
12376
  this.projectRoot = projectRoot;
11154
12377
  this.config = config;
@@ -11164,15 +12387,15 @@ var FileWatcher = class {
11164
12387
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
11165
12388
  this.watcher = chokidar_default.watch(watchTargets, {
11166
12389
  ignored: (filePath) => {
11167
- const relativePath = path15.relative(this.projectRoot, filePath);
12390
+ const relativePath = path17.relative(this.projectRoot, filePath);
11168
12391
  if (!relativePath) return false;
11169
12392
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
11170
12393
  return false;
11171
12394
  }
11172
- if (hasFilteredPathSegment(relativePath, path15.sep)) {
12395
+ if (hasFilteredPathSegment(relativePath, path17.sep)) {
11173
12396
  return true;
11174
12397
  }
11175
- if (isRestrictedDirectory(relativePath, path15.sep)) {
12398
+ if (isRestrictedDirectory(relativePath, path17.sep)) {
11176
12399
  return true;
11177
12400
  }
11178
12401
  if (ignoreFilter.ignores(relativePath)) {
@@ -11187,6 +12410,13 @@ var FileWatcher = class {
11187
12410
  pollInterval: 100
11188
12411
  }
11189
12412
  });
12413
+ this.readyPromise = new Promise((resolve13) => {
12414
+ this.resolveReady = resolve13;
12415
+ });
12416
+ this.watcher.once("ready", () => {
12417
+ this.resolveReady?.();
12418
+ this.resolveReady = null;
12419
+ });
11190
12420
  this.watcher.on("error", (error) => {
11191
12421
  const err = error instanceof Error ? error : null;
11192
12422
  if (err?.code === "EPERM" || err?.code === "EACCES") {
@@ -11218,24 +12448,24 @@ var FileWatcher = class {
11218
12448
  this.scheduleFlush();
11219
12449
  }
11220
12450
  isProjectConfigPath(filePath) {
11221
- const relativePath = path15.relative(this.projectRoot, filePath);
11222
- const normalizedRelativePath = path15.normalize(relativePath);
12451
+ const relativePath = path17.relative(this.projectRoot, filePath);
12452
+ const normalizedRelativePath = path17.normalize(relativePath);
11223
12453
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
11224
12454
  }
11225
12455
  isProjectConfigPathOrAncestor(relativePath) {
11226
- const normalizedRelativePath = path15.normalize(relativePath);
12456
+ const normalizedRelativePath = path17.normalize(relativePath);
11227
12457
  return this.getProjectConfigRelativePaths().some(
11228
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path15.sep}`)
12458
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path17.sep}`)
11229
12459
  );
11230
12460
  }
11231
12461
  getProjectConfigRelativePaths() {
11232
12462
  if (this.configPath) {
11233
- return [path15.normalize(path15.relative(this.projectRoot, this.configPath))];
12463
+ return [path17.normalize(path17.relative(this.projectRoot, this.configPath))];
11234
12464
  }
11235
12465
  return [
11236
12466
  resolveProjectConfigPath(this.projectRoot, this.host),
11237
12467
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
11238
- ].map((configPath) => path15.normalize(path15.relative(this.projectRoot, configPath)));
12468
+ ].map((configPath) => path17.normalize(path17.relative(this.projectRoot, configPath)));
11239
12469
  }
11240
12470
  scheduleFlush() {
11241
12471
  if (this.debounceTimer) {
@@ -11250,7 +12480,7 @@ var FileWatcher = class {
11250
12480
  return;
11251
12481
  }
11252
12482
  const changes = Array.from(this.pendingChanges.entries()).map(
11253
- ([path22, type]) => ({ path: path22, type })
12483
+ ([path24, type]) => ({ path: path24, type })
11254
12484
  );
11255
12485
  this.pendingChanges.clear();
11256
12486
  try {
@@ -11259,25 +12489,32 @@ var FileWatcher = class {
11259
12489
  console.error("Error handling file changes:", error);
11260
12490
  }
11261
12491
  }
11262
- stop() {
12492
+ async stop() {
11263
12493
  if (this.debounceTimer) {
11264
12494
  clearTimeout(this.debounceTimer);
11265
12495
  this.debounceTimer = null;
11266
12496
  }
11267
12497
  if (this.watcher) {
11268
- this.watcher.close();
12498
+ const watcher = this.watcher;
11269
12499
  this.watcher = null;
12500
+ await watcher.close();
11270
12501
  }
12502
+ this.resolveReady?.();
12503
+ this.resolveReady = null;
12504
+ this.readyPromise = null;
11271
12505
  this.pendingChanges.clear();
11272
12506
  this.onChanges = null;
11273
12507
  }
11274
12508
  isRunning() {
11275
12509
  return this.watcher !== null;
11276
12510
  }
12511
+ async waitUntilReady() {
12512
+ await (this.readyPromise ?? Promise.resolve());
12513
+ }
11277
12514
  };
11278
12515
 
11279
12516
  // src/watcher/git-head-watcher.ts
11280
- var path16 = __toESM(require("path"), 1);
12517
+ var path18 = __toESM(require("path"), 1);
11281
12518
  var GitHeadWatcher = class {
11282
12519
  watcher = null;
11283
12520
  projectRoot;
@@ -11299,7 +12536,7 @@ var GitHeadWatcher = class {
11299
12536
  this.onBranchChange = handler;
11300
12537
  this.currentBranch = getCurrentBranch(this.projectRoot);
11301
12538
  const headPath = getHeadPath(this.projectRoot);
11302
- const refsPath = path16.join(this.projectRoot, ".git", "refs", "heads");
12539
+ const refsPath = path18.join(this.projectRoot, ".git", "refs", "heads");
11303
12540
  this.watcher = chokidar_default.watch([headPath, refsPath], {
11304
12541
  persistent: true,
11305
12542
  ignoreInitial: true,
@@ -11336,14 +12573,15 @@ var GitHeadWatcher = class {
11336
12573
  getCurrentBranch() {
11337
12574
  return this.currentBranch;
11338
12575
  }
11339
- stop() {
12576
+ async stop() {
11340
12577
  if (this.debounceTimer) {
11341
12578
  clearTimeout(this.debounceTimer);
11342
12579
  this.debounceTimer = null;
11343
12580
  }
11344
12581
  if (this.watcher) {
11345
- this.watcher.close();
12582
+ const watcher = this.watcher;
11346
12583
  this.watcher = null;
12584
+ await watcher.close();
11347
12585
  }
11348
12586
  this.onBranchChange = null;
11349
12587
  }
@@ -11361,6 +12599,8 @@ var BackgroundReindexer = class {
11361
12599
  running = false;
11362
12600
  pending = false;
11363
12601
  stopped = false;
12602
+ retryTimer = null;
12603
+ retryDelayMs = 50;
11364
12604
  request() {
11365
12605
  if (this.stopped) {
11366
12606
  return;
@@ -11371,9 +12611,13 @@ var BackgroundReindexer = class {
11371
12611
  stop() {
11372
12612
  this.stopped = true;
11373
12613
  this.pending = false;
12614
+ if (this.retryTimer) {
12615
+ clearTimeout(this.retryTimer);
12616
+ this.retryTimer = null;
12617
+ }
11374
12618
  }
11375
12619
  drain() {
11376
- if (this.stopped || this.running || !this.pending) {
12620
+ if (this.stopped || this.running || this.retryTimer || !this.pending) {
11377
12621
  return;
11378
12622
  }
11379
12623
  this.pending = false;
@@ -11381,13 +12625,29 @@ var BackgroundReindexer = class {
11381
12625
  void this.run();
11382
12626
  }
11383
12627
  async run() {
12628
+ let retryAfterContention = false;
11384
12629
  try {
11385
12630
  await this.runIndex();
12631
+ this.retryDelayMs = 50;
11386
12632
  } catch (error) {
11387
- console.error("[codebase-index] Background reindex failed:", error);
12633
+ if (isTransientIndexLockContention(error)) {
12634
+ this.pending = true;
12635
+ retryAfterContention = true;
12636
+ } else {
12637
+ console.error("[codebase-index] Background reindex failed:", error);
12638
+ }
11388
12639
  } finally {
11389
12640
  this.running = false;
11390
- this.drain();
12641
+ if (retryAfterContention && !this.stopped) {
12642
+ const delay = this.retryDelayMs;
12643
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
12644
+ this.retryTimer = setTimeout(() => {
12645
+ this.retryTimer = null;
12646
+ this.drain();
12647
+ }, delay);
12648
+ } else {
12649
+ this.drain();
12650
+ }
11391
12651
  }
11392
12652
  }
11393
12653
  };
@@ -11421,10 +12681,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
11421
12681
  return {
11422
12682
  fileWatcher,
11423
12683
  gitWatcher,
11424
- stop() {
12684
+ whenReady() {
12685
+ return fileWatcher.waitUntilReady();
12686
+ },
12687
+ async stop() {
11425
12688
  backgroundReindexer.stop();
11426
- fileWatcher.stop();
11427
- gitWatcher?.stop();
12689
+ await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
11428
12690
  }
11429
12691
  };
11430
12692
  }
@@ -11530,13 +12792,13 @@ var pr_impact = (0, import_plugin.tool)({
11530
12792
  });
11531
12793
 
11532
12794
  // src/tools/index.ts
11533
- var import_fs10 = require("fs");
11534
- var os4 = __toESM(require("os"), 1);
11535
- var path19 = __toESM(require("path"), 1);
12795
+ var import_fs11 = require("fs");
12796
+ var os5 = __toESM(require("os"), 1);
12797
+ var path21 = __toESM(require("path"), 1);
11536
12798
 
11537
12799
  // src/tools/visualize/activity.ts
11538
- var import_child_process3 = require("child_process");
11539
- var path17 = __toESM(require("path"), 1);
12800
+ var import_child_process4 = require("child_process");
12801
+ var path19 = __toESM(require("path"), 1);
11540
12802
  function attachRecentActivity(data, projectRoot) {
11541
12803
  const activity = readGitActivity(projectRoot);
11542
12804
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -11547,7 +12809,7 @@ function attachRecentActivity(data, projectRoot) {
11547
12809
  }
11548
12810
  function readGitActivity(projectRoot) {
11549
12811
  try {
11550
- const output = (0, import_child_process3.execFileSync)(
12812
+ const output = (0, import_child_process4.execFileSync)(
11551
12813
  "git",
11552
12814
  ["-C", projectRoot, "log", "--since=90.days", "--numstat", "--date=short", "--pretty=format:__COMMIT__%x09%h%x09%ad%x09%s"],
11553
12815
  { encoding: "utf8", maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] }
@@ -11698,7 +12960,7 @@ function normalizePath2(filePath) {
11698
12960
  return filePath.replace(/\\/g, "/");
11699
12961
  }
11700
12962
  function toGitRelativePath(projectRoot, filePath) {
11701
- const relativePath = path17.isAbsolute(filePath) ? path17.relative(projectRoot, filePath) : filePath;
12963
+ const relativePath = path19.isAbsolute(filePath) ? path19.relative(projectRoot, filePath) : filePath;
11702
12964
  return normalizePath2(relativePath);
11703
12965
  }
11704
12966
 
@@ -11956,7 +13218,7 @@ render();
11956
13218
  }
11957
13219
 
11958
13220
  // src/tools/visualize/transform.ts
11959
- var path18 = __toESM(require("path"), 1);
13221
+ var path20 = __toESM(require("path"), 1);
11960
13222
 
11961
13223
  // src/tools/visualize/modules.ts
11962
13224
  var MAX_MODULES = 18;
@@ -12089,8 +13351,8 @@ function compactModules(prefixToNodes) {
12089
13351
  function deriveModules(nodes) {
12090
13352
  const initial = /* @__PURE__ */ new Map();
12091
13353
  for (const node of nodes) {
12092
- const relative10 = stripToProjectRelative(node.filePath);
12093
- const prefix = modulePrefixFromRelativePath(relative10);
13354
+ const relative11 = stripToProjectRelative(node.filePath);
13355
+ const prefix = modulePrefixFromRelativePath(relative11);
12094
13356
  if (!initial.has(prefix)) initial.set(prefix, []);
12095
13357
  initial.get(prefix)?.push(node);
12096
13358
  }
@@ -12216,7 +13478,7 @@ function transformForVisualization(symbols, edges, options = {}) {
12216
13478
  filePath: s.filePath,
12217
13479
  kind: s.kind,
12218
13480
  line: s.startLine,
12219
- directory: path18.dirname(s.filePath),
13481
+ directory: path20.dirname(s.filePath),
12220
13482
  moduleId: "",
12221
13483
  moduleLabel: ""
12222
13484
  }));
@@ -12267,7 +13529,10 @@ var codebase_peek = (0, import_plugin2.tool)({
12267
13529
  limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
12268
13530
  fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12269
13531
  directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12270
- chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type")
13532
+ chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
13533
+ blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
13534
+ blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
13535
+ blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
12271
13536
  },
12272
13537
  async execute(args, context) {
12273
13538
  const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
@@ -12275,7 +13540,10 @@ var codebase_peek = (0, import_plugin2.tool)({
12275
13540
  fileType: args.fileType,
12276
13541
  directory: args.directory,
12277
13542
  chunkType: args.chunkType,
12278
- metadataOnly: true
13543
+ metadataOnly: true,
13544
+ blameAuthor: args.blameAuthor,
13545
+ blameSha: args.blameSha,
13546
+ blameSince: args.blameSince
12279
13547
  });
12280
13548
  return formatCodebasePeek(results);
12281
13549
  }
@@ -12291,7 +13559,9 @@ var index_codebase = (0, import_plugin2.tool)({
12291
13559
  const result = await runIndexCodebase(context?.worktree, DEFAULT_HOST, args, (title, metadata) => {
12292
13560
  context.metadata({ title, metadata });
12293
13561
  });
12294
- return result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
13562
+ if (result.kind === "estimate") return formatCostEstimate(result.estimate);
13563
+ if (result.kind === "busy") return result.text;
13564
+ return formatIndexStats(result.stats, args.verbose ?? false);
12295
13565
  }
12296
13566
  });
12297
13567
  var index_status = (0, import_plugin2.tool)({
@@ -12305,7 +13575,9 @@ var index_health_check = (0, import_plugin2.tool)({
12305
13575
  description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
12306
13576
  args: {},
12307
13577
  async execute(_args, context) {
12308
- return formatHealthCheck(await getIndexHealthCheck(context?.worktree, DEFAULT_HOST));
13578
+ const result = await runIndexHealthCheck(context?.worktree, DEFAULT_HOST);
13579
+ if (result.kind === "busy") return result.text;
13580
+ return formatHealthCheck(result.health);
12309
13581
  }
12310
13582
  });
12311
13583
  var index_metrics = (0, import_plugin2.tool)({
@@ -12358,7 +13630,10 @@ var codebase_search = (0, import_plugin2.tool)({
12358
13630
  fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12359
13631
  directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12360
13632
  chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
12361
- contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
13633
+ contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
13634
+ blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
13635
+ blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
13636
+ blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
12362
13637
  },
12363
13638
  async execute(args, context) {
12364
13639
  const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
@@ -12366,7 +13641,10 @@ var codebase_search = (0, import_plugin2.tool)({
12366
13641
  fileType: args.fileType,
12367
13642
  directory: args.directory,
12368
13643
  chunkType: args.chunkType,
12369
- contextLines: args.contextLines
13644
+ contextLines: args.contextLines,
13645
+ blameAuthor: args.blameAuthor,
13646
+ blameSha: args.blameSha,
13647
+ blameSince: args.blameSince
12370
13648
  });
12371
13649
  if (results.length === 0) {
12372
13650
  return "No matching code found. Try a different query or run index_codebase first.";
@@ -12405,22 +13683,10 @@ var call_graph = (0, import_plugin2.tool)({
12405
13683
  return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
12406
13684
  }
12407
13685
  const { callees } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
12408
- if (callees.length === 0) {
12409
- return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
12410
- }
12411
- return callees.map((edge, index) => {
12412
- const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
12413
- return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
12414
- }).join("\n");
13686
+ return formatCallGraphCallees(args.symbolId, callees, args.relationshipType);
12415
13687
  }
12416
13688
  const { callers } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
12417
- if (callers.length === 0) {
12418
- return `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
12419
- }
12420
- return callers.map((edge, index) => {
12421
- const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
12422
- return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
12423
- }).join("\n");
13689
+ return formatCallGraphCallers(args.name, callers, args.relationshipType);
12424
13690
  }
12425
13691
  });
12426
13692
  var call_graph_path = (0, import_plugin2.tool)({
@@ -12431,17 +13697,8 @@ var call_graph_path = (0, import_plugin2.tool)({
12431
13697
  maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12432
13698
  },
12433
13699
  async execute(args, context) {
12434
- const path22 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
12435
- if (path22.length === 0) {
12436
- return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
12437
- }
12438
- const formatted = path22.map((hop, index) => {
12439
- const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
12440
- const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
12441
- return `${prefix} ${hop.symbolName}${location}`;
12442
- });
12443
- return `Path (${path22.length} hops):
12444
- ${formatted.join("\n")}`;
13700
+ const path24 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
13701
+ return formatCallGraphPath(args.from, args.to, path24);
12445
13702
  }
12446
13703
  });
12447
13704
  var add_knowledge_base = (0, import_plugin2.tool)({
@@ -12494,8 +13751,8 @@ var index_visualize = (0, import_plugin2.tool)({
12494
13751
  return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
12495
13752
  }
12496
13753
  const html = generateVisualizationHtml(vizData);
12497
- const outputPath = path19.join(os4.tmpdir(), `call-graph-${Date.now()}.html`);
12498
- (0, import_fs10.writeFileSync)(outputPath, html, "utf-8");
13754
+ const outputPath = path21.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
13755
+ (0, import_fs11.writeFileSync)(outputPath, html, "utf-8");
12499
13756
  let result = `Temporal call graph visualization generated: ${outputPath}
12500
13757
 
12501
13758
  `;
@@ -12516,8 +13773,8 @@ var index_visualize = (0, import_plugin2.tool)({
12516
13773
  });
12517
13774
 
12518
13775
  // src/commands/loader.ts
12519
- var import_fs11 = require("fs");
12520
- var path20 = __toESM(require("path"), 1);
13776
+ var import_fs12 = require("fs");
13777
+ var path22 = __toESM(require("path"), 1);
12521
13778
  function parseFrontmatter(content) {
12522
13779
  const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
12523
13780
  const match = content.match(frontmatterRegex);
@@ -12538,21 +13795,21 @@ function parseFrontmatter(content) {
12538
13795
  }
12539
13796
  function loadCommandsFromDirectory(commandsDir) {
12540
13797
  const commands = /* @__PURE__ */ new Map();
12541
- if (!(0, import_fs11.existsSync)(commandsDir)) {
13798
+ if (!(0, import_fs12.existsSync)(commandsDir)) {
12542
13799
  return commands;
12543
13800
  }
12544
- const files = (0, import_fs11.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
13801
+ const files = (0, import_fs12.readdirSync)(commandsDir).filter((f) => f.endsWith(".md"));
12545
13802
  for (const file of files) {
12546
- const filePath = path20.join(commandsDir, file);
13803
+ const filePath = path22.join(commandsDir, file);
12547
13804
  let content;
12548
13805
  try {
12549
- content = (0, import_fs11.readFileSync)(filePath, "utf-8");
13806
+ content = (0, import_fs12.readFileSync)(filePath, "utf-8");
12550
13807
  } catch (error) {
12551
13808
  const message = error instanceof Error ? error.message : String(error);
12552
13809
  throw new Error(`Failed to load command file ${filePath}: ${message}`);
12553
13810
  }
12554
13811
  const { frontmatter, body } = parseFrontmatter(content);
12555
- const name = path20.basename(file, ".md");
13812
+ const name = path22.basename(file, ".md");
12556
13813
  const description = frontmatter.description || `Run the ${name} command`;
12557
13814
  commands.set(name, {
12558
13815
  description,
@@ -12856,18 +14113,37 @@ var RoutingHintController = class {
12856
14113
  };
12857
14114
 
12858
14115
  // src/utils/auto-index.ts
14116
+ var INITIAL_RETRY_DELAY_MS = 50;
14117
+ var MAX_RETRY_DELAY_MS = 500;
14118
+ var autoIndexStates = /* @__PURE__ */ new WeakMap();
12859
14119
  function getErrorMessage3(error) {
12860
14120
  return error instanceof Error ? error.message : String(error);
12861
14121
  }
12862
- function startAutoIndex(indexer, projectRoot) {
12863
- indexer.initialize().then(() => {
12864
- indexer.index().catch((error) => {
12865
- console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
12866
- });
14122
+ function runAutoIndex(indexer, projectRoot, state) {
14123
+ void indexer.index().then(() => {
14124
+ autoIndexStates.delete(indexer);
12867
14125
  }).catch((error) => {
12868
- console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14126
+ if (!isTransientIndexLockContention(error)) {
14127
+ autoIndexStates.delete(indexer);
14128
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
14129
+ return;
14130
+ }
14131
+ const retryDelayMs = state.retryDelayMs;
14132
+ state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
14133
+ const retryTimer = setTimeout(() => {
14134
+ runAutoIndex(indexer, projectRoot, state);
14135
+ }, retryDelayMs);
14136
+ retryTimer.unref?.();
12869
14137
  });
12870
14138
  }
14139
+ function startAutoIndex(indexer, projectRoot) {
14140
+ if (autoIndexStates.has(indexer)) return;
14141
+ const state = {
14142
+ retryDelayMs: INITIAL_RETRY_DELAY_MS
14143
+ };
14144
+ autoIndexStates.set(indexer, state);
14145
+ runAutoIndex(indexer, projectRoot, state);
14146
+ }
12871
14147
 
12872
14148
  // src/index.ts
12873
14149
  var import_meta2 = {};
@@ -12885,9 +14161,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
12885
14161
  function getCommandsDir() {
12886
14162
  let currentDir = process.cwd();
12887
14163
  if (typeof import_meta2 !== "undefined" && import_meta2.url) {
12888
- currentDir = path21.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
14164
+ currentDir = path23.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
12889
14165
  }
12890
- return path21.join(currentDir, "..", "commands");
14166
+ return path23.join(currentDir, "..", "commands");
12891
14167
  }
12892
14168
  function appendRoutingHints(output, hints, preferredRole) {
12893
14169
  const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
@@ -12907,7 +14183,7 @@ var plugin = async ({ directory, worktree }) => {
12907
14183
  initializeTools2(projectRoot, config);
12908
14184
  const getProjectIndexer = () => getIndexerForProject2(projectRoot);
12909
14185
  const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
12910
- const isHomeDir = path21.resolve(projectRoot) === path21.resolve(os5.homedir());
14186
+ const isHomeDir = path23.resolve(projectRoot) === path23.resolve(os6.homedir());
12911
14187
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
12912
14188
  if (isHomeDir) {
12913
14189
  console.warn(