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.
@@ -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(path15, checkUnignored, mode) {
498
+ test(path17, 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(path15);
507
+ const matched = rule[mode].test(path17);
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 = (path15, originalPath, doThrow) => {
529
- if (!isString(path15)) {
528
+ var checkPath = (path17, originalPath, doThrow) => {
529
+ if (!isString(path17)) {
530
530
  return doThrow(
531
531
  `path must be a string, but got \`${originalPath}\``,
532
532
  TypeError
533
533
  );
534
534
  }
535
- if (!path15) {
535
+ if (!path17) {
536
536
  return doThrow(`path must not be empty`, TypeError);
537
537
  }
538
- if (checkPath.isNotRelative(path15)) {
538
+ if (checkPath.isNotRelative(path17)) {
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 = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
547
+ var isNotRelative = (path17) => REGEX_TEST_INVALID_PATH.test(path17);
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 path15 = originalPath && checkPath.convert(originalPath);
577
+ const path17 = originalPath && checkPath.convert(originalPath);
578
578
  checkPath(
579
- path15,
579
+ path17,
580
580
  originalPath,
581
581
  this._strictPathCheck ? throwError : RETURN_FALSE
582
582
  );
583
- return this._t(path15, cache, checkUnignored, slices);
583
+ return this._t(path17, cache, checkUnignored, slices);
584
584
  }
585
- checkIgnore(path15) {
586
- if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
587
- return this.test(path15);
585
+ checkIgnore(path17) {
586
+ if (!REGEX_TEST_TRAILING_SLASH.test(path17)) {
587
+ return this.test(path17);
588
588
  }
589
- const slices = path15.split(SLASH).filter(Boolean);
589
+ const slices = path17.split(SLASH).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(path15, false, MODE_CHECK_IGNORE);
602
+ return this._rules.test(path17, false, MODE_CHECK_IGNORE);
603
603
  }
604
- _t(path15, cache, checkUnignored, slices) {
605
- if (path15 in cache) {
606
- return cache[path15];
604
+ _t(path17, cache, checkUnignored, slices) {
605
+ if (path17 in cache) {
606
+ return cache[path17];
607
607
  }
608
608
  if (!slices) {
609
- slices = path15.split(SLASH).filter(Boolean);
609
+ slices = path17.split(SLASH).filter(Boolean);
610
610
  }
611
611
  slices.pop();
612
612
  if (!slices.length) {
613
- return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
613
+ return cache[path17] = this._rules.test(path17, checkUnignored, MODE_IGNORE);
614
614
  }
615
615
  const parent = this._t(
616
616
  slices.join(SLASH) + SLASH,
@@ -618,29 +618,29 @@ var require_ignore = __commonJS({
618
618
  checkUnignored,
619
619
  slices
620
620
  );
621
- return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
621
+ return cache[path17] = parent.ignored ? parent : this._rules.test(path17, checkUnignored, MODE_IGNORE);
622
622
  }
623
- ignores(path15) {
624
- return this._test(path15, this._ignoreCache, false).ignored;
623
+ ignores(path17) {
624
+ return this._test(path17, this._ignoreCache, false).ignored;
625
625
  }
626
626
  createFilter() {
627
- return (path15) => !this.ignores(path15);
627
+ return (path17) => !this.ignores(path17);
628
628
  }
629
629
  filter(paths) {
630
630
  return makeArray(paths).filter(this.createFilter());
631
631
  }
632
632
  // @returns {TestResult}
633
- test(path15) {
634
- return this._test(path15, this._testCache, true);
633
+ test(path17) {
634
+ return this._test(path17, this._testCache, true);
635
635
  }
636
636
  };
637
637
  var factory = (options) => new Ignore2(options);
638
- var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
638
+ var isPathValid = (path17) => checkPath(path17 && checkPath.convert(path17), path17, 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 = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
643
+ checkPath.isNotRelative = (path17) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path17) || isNotRelative(path17);
644
644
  };
645
645
  if (
646
646
  // Detect `process` so that it can run in browsers.
@@ -661,14 +661,14 @@ __export(pi_extension_exports, {
661
661
  default: () => codebaseIndexPiExtension
662
662
  });
663
663
  module.exports = __toCommonJS(pi_extension_exports);
664
- var import_typebox = require("typebox");
664
+ var import_typebox2 = require("typebox");
665
665
 
666
666
  // src/config/constants.ts
667
667
  var DEFAULT_INCLUDE = [
668
- "**/*.{ts,tsx,js,jsx,mjs,cjs}",
668
+ "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
669
669
  "**/*.{py,pyi}",
670
- "**/*.{go,rs,java,kt,scala}",
671
- "**/*.{c,cpp,cc,h,hpp}",
670
+ "**/*.{go,rs,java,cs,kt,scala}",
671
+ "**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
672
672
  "**/*.{rb,php,inc,swift}",
673
673
  "**/*.{cls,trigger}",
674
674
  "**/*.{vue,svelte,astro}",
@@ -793,7 +793,8 @@ function getDefaultIndexingConfig() {
793
793
  requireProjectMarker: true,
794
794
  maxDepth: 5,
795
795
  maxFilesPerDirectory: 100,
796
- fallbackToTextOnMaxChunks: true
796
+ fallbackToTextOnMaxChunks: true,
797
+ gitBlame: { enabled: false }
797
798
  };
798
799
  }
799
800
  function getDefaultSearchConfig() {
@@ -917,7 +918,10 @@ function parseConfig(raw) {
917
918
  requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
918
919
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
919
920
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
920
- fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
921
+ fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
922
+ gitBlame: {
923
+ enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
924
+ }
921
925
  };
922
926
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
923
927
  const search = {
@@ -1333,8 +1337,8 @@ function formatPrImpact(result) {
1333
1337
  }
1334
1338
 
1335
1339
  // src/tools/operations.ts
1336
- var import_fs9 = require("fs");
1337
- var path14 = __toESM(require("path"), 1);
1340
+ var import_fs10 = require("fs");
1341
+ var path16 = __toESM(require("path"), 1);
1338
1342
 
1339
1343
  // src/config/merger.ts
1340
1344
  var import_fs5 = require("fs");
@@ -1504,21 +1508,21 @@ function getProjectIndexRelativePath(host) {
1504
1508
  return CODEBASE_PROJECT_INDEX_RELATIVE_PATH;
1505
1509
  }
1506
1510
  }
1507
- function resolveWorktreeFallbackPath(projectRoot2, relativePath) {
1508
- const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot2);
1511
+ function resolveWorktreeFallbackPath(projectRoot3, relativePath) {
1512
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot3);
1509
1513
  if (!mainRepoRoot) {
1510
1514
  return null;
1511
1515
  }
1512
1516
  const fallbackPath = path4.join(mainRepoRoot, relativePath);
1513
1517
  return (0, import_fs4.existsSync)(fallbackPath) ? fallbackPath : null;
1514
1518
  }
1515
- function resolveWorktreeFallbackProjectIndexPath(projectRoot2, host) {
1516
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot2, getProjectIndexRelativePath(host));
1519
+ function resolveWorktreeFallbackProjectIndexPath(projectRoot3, host) {
1520
+ const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1517
1521
  if (inheritedHostPath) {
1518
1522
  return inheritedHostPath;
1519
1523
  }
1520
1524
  if (host !== "opencode") {
1521
- return resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1525
+ return resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1522
1526
  }
1523
1527
  return null;
1524
1528
  }
@@ -1528,8 +1532,11 @@ function getHostProjectConfigRelativePath(host) {
1528
1532
  function getHostProjectIndexRelativePath(host) {
1529
1533
  return getProjectIndexRelativePath(host);
1530
1534
  }
1531
- function hasHostProjectConfig(projectRoot2, host) {
1532
- return (0, import_fs4.existsSync)(path4.join(projectRoot2, getProjectConfigRelativePath(host)));
1535
+ function hasHostProjectConfig(projectRoot3, host) {
1536
+ return (0, import_fs4.existsSync)(path4.join(projectRoot3, getProjectConfigRelativePath(host)));
1537
+ }
1538
+ function hasHostGlobalConfig(host) {
1539
+ return (0, import_fs4.existsSync)(getGlobalConfigPath(host));
1533
1540
  }
1534
1541
  function getGlobalIndexPath(host = "opencode") {
1535
1542
  switch (host) {
@@ -1570,6 +1577,9 @@ function resolveGlobalIndexPath(host = "opencode") {
1570
1577
  return hostIndexPath;
1571
1578
  }
1572
1579
  if (host !== "opencode") {
1580
+ if (hasHostGlobalConfig(host)) {
1581
+ return hostIndexPath;
1582
+ }
1573
1583
  const legacyIndexPath = getGlobalIndexPath("opencode");
1574
1584
  if ((0, import_fs4.existsSync)(legacyIndexPath)) {
1575
1585
  return legacyIndexPath;
@@ -1577,55 +1587,55 @@ function resolveGlobalIndexPath(host = "opencode") {
1577
1587
  }
1578
1588
  return hostIndexPath;
1579
1589
  }
1580
- function resolveProjectConfigPath(projectRoot2, host = "opencode") {
1581
- const hostConfigPath = path4.join(projectRoot2, getProjectConfigRelativePath(host));
1590
+ function resolveProjectConfigPath(projectRoot3, host = "opencode") {
1591
+ const hostConfigPath = path4.join(projectRoot3, getProjectConfigRelativePath(host));
1582
1592
  if ((0, import_fs4.existsSync)(hostConfigPath)) {
1583
1593
  return hostConfigPath;
1584
1594
  }
1585
1595
  if (host !== "opencode") {
1586
- const legacyConfigPath = path4.join(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1596
+ const legacyConfigPath = path4.join(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1587
1597
  if ((0, import_fs4.existsSync)(legacyConfigPath)) {
1588
1598
  return legacyConfigPath;
1589
1599
  }
1590
1600
  }
1591
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectConfigRelativePath(host));
1601
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectConfigRelativePath(host));
1592
1602
  if (hostFallback) {
1593
1603
  return hostFallback;
1594
1604
  }
1595
1605
  if (host !== "opencode") {
1596
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1606
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1597
1607
  if (legacyFallback) {
1598
1608
  return legacyFallback;
1599
1609
  }
1600
1610
  }
1601
1611
  return hostConfigPath;
1602
1612
  }
1603
- function resolveWritableProjectConfigPath(projectRoot2, host = "opencode") {
1604
- return path4.join(projectRoot2, getProjectConfigRelativePath(host));
1613
+ function resolveWritableProjectConfigPath(projectRoot3, host = "opencode") {
1614
+ return path4.join(projectRoot3, getProjectConfigRelativePath(host));
1605
1615
  }
1606
- function resolveProjectIndexPath(projectRoot2, scope, host = "opencode") {
1616
+ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1607
1617
  if (scope === "global") {
1608
1618
  return resolveGlobalIndexPath(host);
1609
1619
  }
1610
- const localIndexPath = path4.join(projectRoot2, getProjectIndexRelativePath(host));
1620
+ const localIndexPath = path4.join(projectRoot3, getProjectIndexRelativePath(host));
1611
1621
  if ((0, import_fs4.existsSync)(localIndexPath)) {
1612
1622
  return localIndexPath;
1613
1623
  }
1614
1624
  if (host !== "opencode") {
1615
- const legacyIndexPath = path4.join(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1616
- if ((0, import_fs4.existsSync)(legacyIndexPath) && !hasHostProjectConfig(projectRoot2, host)) {
1625
+ const legacyIndexPath = path4.join(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1626
+ if ((0, import_fs4.existsSync)(legacyIndexPath) && !hasHostProjectConfig(projectRoot3, host)) {
1617
1627
  return legacyIndexPath;
1618
1628
  }
1619
1629
  }
1620
- if (hasHostProjectConfig(projectRoot2, host)) {
1630
+ if (hasHostProjectConfig(projectRoot3, host)) {
1621
1631
  return localIndexPath;
1622
1632
  }
1623
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectIndexRelativePath(host));
1633
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1624
1634
  if (hostFallback) {
1625
1635
  return hostFallback;
1626
1636
  }
1627
1637
  if (host !== "opencode") {
1628
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1638
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1629
1639
  if (legacyFallback) {
1630
1640
  return legacyFallback;
1631
1641
  }
@@ -1758,14 +1768,14 @@ function loadJsonFile(filePath) {
1758
1768
  throw new Error(`Failed to load config file ${filePath}: ${message}`);
1759
1769
  }
1760
1770
  }
1761
- function materializeLocalProjectConfig(projectRoot2, config, host = "opencode") {
1762
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot2, host);
1771
+ function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
1772
+ const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
1763
1773
  (0, import_fs5.mkdirSync)(path7.dirname(localConfigPath), { recursive: true });
1764
1774
  (0, import_fs5.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1765
1775
  return localConfigPath;
1766
1776
  }
1767
- function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1768
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1777
+ function loadProjectConfigLayer(projectRoot3, host = "opencode") {
1778
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1769
1779
  const projectConfig = loadJsonFile(projectConfigPath);
1770
1780
  if (!projectConfig) {
1771
1781
  return {};
@@ -1776,14 +1786,14 @@ function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1776
1786
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1777
1787
  normalizedConfig.knowledgeBases,
1778
1788
  projectConfigBaseDir,
1779
- projectRoot2
1789
+ projectRoot3
1780
1790
  );
1781
1791
  }
1782
1792
  return normalizedConfig;
1783
1793
  }
1784
- function loadMergedConfig(projectRoot2, host = "opencode") {
1794
+ function loadMergedConfig(projectRoot3, host = "opencode") {
1785
1795
  const globalConfigPath = resolveGlobalConfigPath(host);
1786
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1796
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1787
1797
  let globalConfig = null;
1788
1798
  let globalConfigError = null;
1789
1799
  try {
@@ -1792,7 +1802,7 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1792
1802
  globalConfigError = error instanceof Error ? error : new Error(String(error));
1793
1803
  }
1794
1804
  const projectConfig = loadJsonFile(projectConfigPath);
1795
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot2, host);
1805
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
1796
1806
  if (globalConfigError) {
1797
1807
  if (!projectConfig) {
1798
1808
  throw globalConfigError;
@@ -1835,11 +1845,11 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1835
1845
  }
1836
1846
 
1837
1847
  // src/indexer/index.ts
1838
- var import_fs7 = require("fs");
1839
- var path11 = __toESM(require("path"), 1);
1848
+ var import_fs8 = require("fs");
1849
+ var path13 = __toESM(require("path"), 1);
1840
1850
  var import_perf_hooks = require("perf_hooks");
1841
- var import_child_process2 = require("child_process");
1842
- var import_util2 = require("util");
1851
+ var import_child_process3 = require("child_process");
1852
+ var import_util3 = require("util");
1843
1853
 
1844
1854
  // node_modules/eventemitter3/index.mjs
1845
1855
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -2943,17 +2953,17 @@ function validateExternalUrl(urlString) {
2943
2953
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2944
2954
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2945
2955
  }
2946
- const hostname = parsed.hostname.toLowerCase();
2947
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2948
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
2956
+ const hostname2 = parsed.hostname.toLowerCase();
2957
+ if (BLOCKED_HOSTNAMES.has(hostname2)) {
2958
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
2949
2959
  }
2950
2960
  for (const pattern of BLOCKED_METADATA_IPS) {
2951
- if (pattern.test(hostname)) {
2952
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
2961
+ if (pattern.test(hostname2)) {
2962
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2953
2963
  }
2954
2964
  }
2955
- if (/^169\.254\./.test(hostname)) {
2956
- return { valid: false, reason: `Blocked: link-local address (${hostname})` };
2965
+ if (/^169\.254\./.test(hostname2)) {
2966
+ return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
2957
2967
  }
2958
2968
  return { valid: true };
2959
2969
  }
@@ -3147,10 +3157,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3147
3157
  }
3148
3158
  const batchResults = await Promise.all(
3149
3159
  batches.map(async (batch) => {
3150
- const requests = batch.map((text2) => ({
3160
+ const requests = batch.map((text3) => ({
3151
3161
  model: `models/${this.modelInfo.model}`,
3152
3162
  content: {
3153
- parts: [{ text: text2 }]
3163
+ parts: [{ text: text3 }]
3154
3164
  },
3155
3165
  taskType,
3156
3166
  outputDimensionality: this.modelInfo.dimensions
@@ -3173,7 +3183,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3173
3183
  const data = await response.json();
3174
3184
  return {
3175
3185
  embeddings: data.embeddings.map((e) => e.values),
3176
- tokensUsed: batch.reduce((sum, text2) => sum + Math.ceil(text2.length / 4), 0)
3186
+ tokensUsed: batch.reduce((sum, text3) => sum + Math.ceil(text3.length / 4), 0)
3177
3187
  };
3178
3188
  })
3179
3189
  );
@@ -3190,28 +3200,28 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3190
3200
  constructor(credentials, modelInfo) {
3191
3201
  super(credentials, modelInfo);
3192
3202
  }
3193
- estimateTokens(text2) {
3194
- return Math.ceil(text2.length / 4);
3203
+ estimateTokens(text3) {
3204
+ return Math.ceil(text3.length / 4);
3195
3205
  }
3196
- truncateToCharLimit(text2, maxChars) {
3197
- if (text2.length <= maxChars) {
3198
- return text2;
3206
+ truncateToCharLimit(text3, maxChars) {
3207
+ if (text3.length <= maxChars) {
3208
+ return text3;
3199
3209
  }
3200
- return `${text2.slice(0, Math.max(0, maxChars - 17))}
3210
+ return `${text3.slice(0, Math.max(0, maxChars - 17))}
3201
3211
  ... [truncated]`;
3202
3212
  }
3203
3213
  isContextLengthError(error) {
3204
3214
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
3205
3215
  return message.includes("context length") && (message.includes("exceed") || message.includes("exceeded") || message.includes("too long")) || message.includes("input length exceeds the context length") || message.includes("context length exceeded");
3206
3216
  }
3207
- buildTruncationCandidates(text2) {
3217
+ buildTruncationCandidates(text3) {
3208
3218
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
3209
3219
  const candidateLimits = /* @__PURE__ */ new Set();
3210
- const baselineLimit = text2.length > baseMaxChars ? baseMaxChars : Math.max(
3220
+ const baselineLimit = text3.length > baseMaxChars ? baseMaxChars : Math.max(
3211
3221
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3212
- Math.floor(text2.length * 0.9)
3222
+ Math.floor(text3.length * 0.9)
3213
3223
  );
3214
- if (baselineLimit < text2.length) {
3224
+ if (baselineLimit < text3.length) {
3215
3225
  candidateLimits.add(baselineLimit);
3216
3226
  }
3217
3227
  for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
@@ -3219,19 +3229,19 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3219
3229
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3220
3230
  Math.floor(baselineLimit * factor)
3221
3231
  );
3222
- if (scaledLimit < text2.length) {
3232
+ if (scaledLimit < text3.length) {
3223
3233
  candidateLimits.add(scaledLimit);
3224
3234
  }
3225
3235
  }
3226
- candidateLimits.add(Math.min(text2.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3236
+ candidateLimits.add(Math.min(text3.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3227
3237
  const candidates = [];
3228
3238
  const seen = /* @__PURE__ */ new Set();
3229
3239
  for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
3230
- if (limit <= 0 || limit >= text2.length) {
3240
+ if (limit <= 0 || limit >= text3.length) {
3231
3241
  continue;
3232
3242
  }
3233
- const truncated = this.truncateToCharLimit(text2, limit);
3234
- if (truncated === text2 || seen.has(truncated)) {
3243
+ const truncated = this.truncateToCharLimit(text3, limit);
3244
+ if (truncated === text3 || seen.has(truncated)) {
3235
3245
  continue;
3236
3246
  }
3237
3247
  seen.add(truncated);
@@ -3239,15 +3249,15 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3239
3249
  }
3240
3250
  return candidates;
3241
3251
  }
3242
- async embedSingleWithFallback(text2) {
3252
+ async embedSingleWithFallback(text3) {
3243
3253
  try {
3244
- return await this.embedSingle(text2);
3254
+ return await this.embedSingle(text3);
3245
3255
  } catch (error) {
3246
3256
  if (!this.isContextLengthError(error)) {
3247
3257
  throw error;
3248
3258
  }
3249
3259
  let lastError = error;
3250
- for (const truncated of this.buildTruncationCandidates(text2)) {
3260
+ for (const truncated of this.buildTruncationCandidates(text3)) {
3251
3261
  try {
3252
3262
  return await this.embedSingle(truncated);
3253
3263
  } catch (retryError) {
@@ -3260,7 +3270,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3260
3270
  throw lastError;
3261
3271
  }
3262
3272
  }
3263
- async embedSingle(text2) {
3273
+ async embedSingle(text3) {
3264
3274
  const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3265
3275
  method: "POST",
3266
3276
  headers: {
@@ -3268,7 +3278,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3268
3278
  },
3269
3279
  body: JSON.stringify({
3270
3280
  model: this.modelInfo.model,
3271
- prompt: text2,
3281
+ prompt: text3,
3272
3282
  truncate: false
3273
3283
  })
3274
3284
  });
@@ -3279,13 +3289,13 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3279
3289
  const data = await response.json();
3280
3290
  return {
3281
3291
  embedding: data.embedding,
3282
- tokensUsed: this.estimateTokens(text2)
3292
+ tokensUsed: this.estimateTokens(text3)
3283
3293
  };
3284
3294
  }
3285
3295
  async embedBatch(texts) {
3286
3296
  const results = [];
3287
- for (const text2 of texts) {
3288
- results.push(await this.embedSingleWithFallback(text2));
3297
+ for (const text3 of texts) {
3298
+ results.push(await this.embedSingleWithFallback(text3));
3289
3299
  }
3290
3300
  return {
3291
3301
  embeddings: results.map((r) => r.embedding),
@@ -3426,7 +3436,7 @@ var SiliconFlowReranker = class {
3426
3436
  var import_ignore = __toESM(require_ignore(), 1);
3427
3437
  var import_fs6 = require("fs");
3428
3438
  var path8 = __toESM(require("path"), 1);
3429
- function createIgnoreFilter(projectRoot2) {
3439
+ function createIgnoreFilter(projectRoot3) {
3430
3440
  const ig = (0, import_ignore.default)();
3431
3441
  const defaultIgnores = [
3432
3442
  "node_modules",
@@ -3447,7 +3457,7 @@ function createIgnoreFilter(projectRoot2) {
3447
3457
  "**/*build*/**"
3448
3458
  ];
3449
3459
  ig.add(defaultIgnores);
3450
- const gitignorePath = path8.join(projectRoot2, ".gitignore");
3460
+ const gitignorePath = path8.join(projectRoot3, ".gitignore");
3451
3461
  if ((0, import_fs6.existsSync)(gitignorePath)) {
3452
3462
  const gitignoreContent = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
3453
3463
  ig.add(gitignoreContent);
@@ -3469,13 +3479,13 @@ function matchGlob(filePath, pattern) {
3469
3479
  const regex = new RegExp(`^${regexPattern}$`);
3470
3480
  return regex.test(filePath);
3471
3481
  }
3472
- async function* walkDirectory(dir, projectRoot2, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3482
+ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3473
3483
  const entries = await import_fs6.promises.readdir(dir, { withFileTypes: true });
3474
3484
  const filesInDir = [];
3475
3485
  const subdirs = [];
3476
3486
  for (const entry of entries) {
3477
3487
  const fullPath = path8.join(dir, entry.name);
3478
- const relativePath = path8.relative(projectRoot2, fullPath);
3488
+ const relativePath = path8.relative(projectRoot3, fullPath);
3479
3489
  if (isHiddenPathSegment(entry.name)) {
3480
3490
  if (entry.isDirectory()) {
3481
3491
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3524,14 +3534,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3524
3534
  yield f;
3525
3535
  }
3526
3536
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3527
- skipped.push({ path: path8.relative(projectRoot2, filesInDir[i].path), reason: "excluded" });
3537
+ skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3528
3538
  }
3529
3539
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3530
3540
  if (canRecurse) {
3531
3541
  for (const sub of subdirs) {
3532
3542
  yield* walkDirectory(
3533
3543
  sub.fullPath,
3534
- projectRoot2,
3544
+ projectRoot3,
3535
3545
  includePatterns,
3536
3546
  excludePatterns,
3537
3547
  ignoreFilter,
@@ -3543,14 +3553,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3543
3553
  }
3544
3554
  }
3545
3555
  }
3546
- async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3556
+ async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3547
3557
  const opts = walkOptions ?? { maxDepth: 5, maxFilesPerDirectory: 100 };
3548
- const ignoreFilter = createIgnoreFilter(projectRoot2);
3558
+ const ignoreFilter = createIgnoreFilter(projectRoot3);
3549
3559
  const files = [];
3550
3560
  const skipped = [];
3551
3561
  for await (const file of walkDirectory(
3552
- projectRoot2,
3553
- projectRoot2,
3562
+ projectRoot3,
3563
+ projectRoot3,
3554
3564
  includePatterns,
3555
3565
  excludePatterns,
3556
3566
  ignoreFilter,
@@ -3565,7 +3575,7 @@ async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxF
3565
3575
  const normalizedRoots = /* @__PURE__ */ new Set();
3566
3576
  for (const kbRoot of additionalRoots) {
3567
3577
  const resolved = path8.normalize(
3568
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot2, kbRoot)
3578
+ path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
3569
3579
  );
3570
3580
  normalizedRoots.add(resolved);
3571
3581
  }
@@ -3991,6 +4001,12 @@ function createMockNativeBinding() {
3991
4001
  constructor() {
3992
4002
  throw error;
3993
4003
  }
4004
+ static openReadOnly() {
4005
+ throw error;
4006
+ }
4007
+ static createEmptyReadOnly() {
4008
+ throw error;
4009
+ }
3994
4010
  close() {
3995
4011
  throw error;
3996
4012
  }
@@ -4094,6 +4110,12 @@ var VectorStore = class {
4094
4110
  load() {
4095
4111
  this.inner.load();
4096
4112
  }
4113
+ loadStrict() {
4114
+ this.inner.loadStrict();
4115
+ }
4116
+ hasFingerprint() {
4117
+ return this.inner.hasFingerprint();
4118
+ }
4097
4119
  count() {
4098
4120
  return this.inner.count();
4099
4121
  }
@@ -4132,8 +4154,8 @@ var VectorStore = class {
4132
4154
  var CHARS_PER_TOKEN = 4;
4133
4155
  var MAX_BATCH_TOKENS = 7500;
4134
4156
  var MAX_SINGLE_CHUNK_TOKENS = 2e3;
4135
- function estimateTokens(text2) {
4136
- return Math.ceil(text2.length / CHARS_PER_TOKEN);
4157
+ function estimateTokens(text3) {
4158
+ return Math.ceil(text3.length / CHARS_PER_TOKEN);
4137
4159
  }
4138
4160
  function getEmbeddingHeaderParts(chunk, filePath) {
4139
4161
  const parts = [];
@@ -4376,12 +4398,24 @@ var InvertedIndex = class {
4376
4398
  return this.inner.documentCount();
4377
4399
  }
4378
4400
  };
4379
- var Database = class {
4401
+ var Database = class _Database {
4380
4402
  inner;
4381
4403
  closed = false;
4382
4404
  constructor(dbPath) {
4383
4405
  this.inner = new native.Database(dbPath);
4384
4406
  }
4407
+ static fromNative(inner) {
4408
+ const database = Object.create(_Database.prototype);
4409
+ database.inner = inner;
4410
+ database.closed = false;
4411
+ return database;
4412
+ }
4413
+ static openReadOnly(dbPath) {
4414
+ return _Database.fromNative(native.Database.openReadOnly(dbPath));
4415
+ }
4416
+ static createEmptyReadOnly() {
4417
+ return _Database.fromNative(native.Database.createEmptyReadOnly());
4418
+ }
4385
4419
  throwIfClosed() {
4386
4420
  if (this.closed) {
4387
4421
  throw new Error("Database is closed");
@@ -4667,20 +4701,20 @@ function getErrorMessage(error) {
4667
4701
  return String(error);
4668
4702
  }
4669
4703
  async function getChangedFiles(opts) {
4670
- const { pr, branch, projectRoot: projectRoot2, baseBranch = "main" } = opts;
4704
+ const { pr, branch, projectRoot: projectRoot3, baseBranch = "main" } = opts;
4671
4705
  if (pr !== void 0) {
4672
- return getChangedFilesForPr(pr, projectRoot2, baseBranch);
4706
+ return getChangedFilesForPr(pr, projectRoot3, baseBranch);
4673
4707
  }
4674
- return getChangedFilesForBranch(branch, projectRoot2, baseBranch);
4708
+ return getChangedFilesForBranch(branch, projectRoot3, baseBranch);
4675
4709
  }
4676
- async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4710
+ async function getChangedFilesForPr(pr, projectRoot3, baseBranch) {
4677
4711
  let headRefName;
4678
4712
  let actualBaseBranch = baseBranch;
4679
4713
  try {
4680
4714
  const { stdout } = await execFileAsync(
4681
4715
  "gh",
4682
4716
  ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
4683
- { cwd: projectRoot2, timeout: 3e4 }
4717
+ { cwd: projectRoot3, timeout: 3e4 }
4684
4718
  );
4685
4719
  const data = JSON.parse(stdout);
4686
4720
  headRefName = data.headRefName;
@@ -4689,7 +4723,7 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4689
4723
  return {
4690
4724
  files: normalizeFiles(
4691
4725
  data.files.map((f) => f.path),
4692
- projectRoot2
4726
+ projectRoot3
4693
4727
  ),
4694
4728
  baseBranch: actualBaseBranch,
4695
4729
  source: "gh",
@@ -4706,49 +4740,49 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4706
4740
  `PR #${pr} returned no usable branch or file information.`
4707
4741
  );
4708
4742
  }
4709
- const result = await getChangedFilesForBranch(headRefName, projectRoot2, actualBaseBranch);
4743
+ const result = await getChangedFilesForBranch(headRefName, projectRoot3, actualBaseBranch);
4710
4744
  return { ...result, headRefName };
4711
4745
  }
4712
- async function getChangedFilesForBranch(branch, projectRoot2, baseBranch) {
4713
- const targetBranch = branch || await getCurrentBranch2(projectRoot2);
4714
- const mergeBase = await getMergeBase(projectRoot2, baseBranch, targetBranch);
4746
+ async function getChangedFilesForBranch(branch, projectRoot3, baseBranch) {
4747
+ const targetBranch = branch || await getCurrentBranch2(projectRoot3);
4748
+ const mergeBase = await getMergeBase(projectRoot3, baseBranch, targetBranch);
4715
4749
  const { stdout } = await execFileAsync(
4716
4750
  "git",
4717
4751
  ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
4718
- { cwd: projectRoot2, timeout: 3e4 }
4752
+ { cwd: projectRoot3, timeout: 3e4 }
4719
4753
  );
4720
4754
  return {
4721
- files: normalizeFiles(stdout.split("\n"), projectRoot2),
4755
+ files: normalizeFiles(stdout.split("\n"), projectRoot3),
4722
4756
  baseBranch,
4723
4757
  source: "git",
4724
4758
  headRefName: targetBranch
4725
4759
  };
4726
4760
  }
4727
- async function getCurrentBranch2(projectRoot2) {
4761
+ async function getCurrentBranch2(projectRoot3) {
4728
4762
  const { stdout } = await execFileAsync(
4729
4763
  "git",
4730
4764
  ["branch", "--show-current"],
4731
- { cwd: projectRoot2, timeout: 3e4 }
4765
+ { cwd: projectRoot3, timeout: 3e4 }
4732
4766
  );
4733
4767
  return stdout.trim() || "HEAD";
4734
4768
  }
4735
- async function getMergeBase(projectRoot2, baseBranch, branch) {
4769
+ async function getMergeBase(projectRoot3, baseBranch, branch) {
4736
4770
  const { stdout } = await execFileAsync(
4737
4771
  "git",
4738
4772
  ["merge-base", baseBranch, branch],
4739
- { cwd: projectRoot2, timeout: 3e4 }
4773
+ { cwd: projectRoot3, timeout: 3e4 }
4740
4774
  );
4741
4775
  return stdout.trim();
4742
4776
  }
4743
- function normalizeFiles(rawFiles, projectRoot2) {
4777
+ function normalizeFiles(rawFiles, projectRoot3) {
4744
4778
  const seen = /* @__PURE__ */ new Set();
4745
4779
  const result = [];
4746
4780
  for (const raw of rawFiles) {
4747
4781
  const trimmed = raw.trim();
4748
4782
  if (!trimmed) continue;
4749
- const absolute = path10.resolve(projectRoot2, trimmed);
4750
- const relative6 = path10.relative(projectRoot2, absolute);
4751
- const cleaned = relative6.startsWith("./") ? relative6.slice(2) : relative6;
4783
+ const absolute = path10.resolve(projectRoot3, trimmed);
4784
+ const relative7 = path10.relative(projectRoot3, absolute);
4785
+ const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
4752
4786
  if (!seen.has(cleaned)) {
4753
4787
  seen.add(cleaned);
4754
4788
  result.push(cleaned);
@@ -4757,8 +4791,473 @@ function normalizeFiles(rawFiles, projectRoot2) {
4757
4791
  return result;
4758
4792
  }
4759
4793
 
4794
+ // src/indexer/git-blame.ts
4795
+ var import_child_process2 = require("child_process");
4796
+ var path11 = __toESM(require("path"), 1);
4797
+ var import_util2 = require("util");
4798
+ var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
4799
+ function parseGitBlamePorcelain(output) {
4800
+ const commits = /* @__PURE__ */ new Map();
4801
+ let current;
4802
+ for (const line of output.split("\n")) {
4803
+ if (/^[0-9a-f]{40} /.test(line)) {
4804
+ const sha = line.slice(0, 40);
4805
+ current = commits.get(sha) ?? {
4806
+ sha,
4807
+ author: "",
4808
+ authorEmail: "",
4809
+ committedAt: 0,
4810
+ summary: "",
4811
+ lines: 0
4812
+ };
4813
+ commits.set(sha, current);
4814
+ continue;
4815
+ }
4816
+ if (!current) {
4817
+ continue;
4818
+ }
4819
+ if (line.startsWith("author ")) {
4820
+ current.author = line.slice("author ".length);
4821
+ } else if (line.startsWith("author-mail ")) {
4822
+ current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
4823
+ } else if (line.startsWith("author-time ")) {
4824
+ current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
4825
+ } else if (line.startsWith("summary ")) {
4826
+ current.summary = line.slice("summary ".length);
4827
+ } else if (line.startsWith(" ")) {
4828
+ current.lines += 1;
4829
+ }
4830
+ }
4831
+ return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4832
+ }
4833
+ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4834
+ const relativePath = path11.relative(projectRoot3, filePath);
4835
+ try {
4836
+ const { stdout } = await execFileAsync2(
4837
+ "git",
4838
+ ["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
4839
+ { cwd: projectRoot3, timeout: 3e4 }
4840
+ );
4841
+ return parseGitBlamePorcelain(stdout);
4842
+ } catch {
4843
+ return void 0;
4844
+ }
4845
+ }
4846
+
4847
+ // src/indexer/index-lock.ts
4848
+ var import_crypto = require("crypto");
4849
+ var import_fs7 = require("fs");
4850
+ var os4 = __toESM(require("os"), 1);
4851
+ var path12 = __toESM(require("path"), 1);
4852
+ var OWNER_FILE_NAME = "owner.json";
4853
+ var RECLAIM_DIRECTORY_NAME = "reclaiming";
4854
+ var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
4855
+ 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;
4856
+ var VALID_OPERATIONS = /* @__PURE__ */ new Set([
4857
+ "initialize",
4858
+ "index",
4859
+ "force-index",
4860
+ "clear",
4861
+ "health-check",
4862
+ "retry-failed-batches",
4863
+ "recovery"
4864
+ ]);
4865
+ var temporaryCounter = 0;
4866
+ function getErrorCode(error) {
4867
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
4868
+ }
4869
+ function retryTransientFilesystemOperation(operation) {
4870
+ let lastError;
4871
+ for (let attempt = 0; attempt < 3; attempt += 1) {
4872
+ try {
4873
+ operation();
4874
+ return;
4875
+ } catch (error) {
4876
+ lastError = error;
4877
+ const code = getErrorCode(error);
4878
+ if (code !== "EBUSY" && code !== "EPERM") throw error;
4879
+ if (attempt < 2) {
4880
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
4881
+ }
4882
+ }
4883
+ }
4884
+ throw lastError;
4885
+ }
4886
+ function parseOwner(value) {
4887
+ if (typeof value !== "object" || value === null) return null;
4888
+ const candidate = value;
4889
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4890
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4891
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4892
+ if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
4893
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4894
+ return candidate;
4895
+ }
4896
+ function parseReclaimOwner(value) {
4897
+ if (typeof value !== "object" || value === null) return null;
4898
+ const candidate = value;
4899
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4900
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4901
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4902
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4903
+ if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
4904
+ return candidate;
4905
+ }
4906
+ function readJsonDirectory(directoryPath, parser) {
4907
+ try {
4908
+ return parser(JSON.parse((0, import_fs7.readFileSync)(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4909
+ } catch {
4910
+ return null;
4911
+ }
4912
+ }
4913
+ function readDirectoryOwner(lockPath) {
4914
+ return readJsonDirectory(lockPath, parseOwner);
4915
+ }
4916
+ function readReclaimOwner(markerPath) {
4917
+ return readJsonDirectory(markerPath, parseReclaimOwner);
4918
+ }
4919
+ function readRecoveryOwner(markerPath) {
4920
+ return readDirectoryOwner(markerPath);
4921
+ }
4922
+ function readLegacyOwner(lockPath) {
4923
+ try {
4924
+ const parsed = JSON.parse((0, import_fs7.readFileSync)(lockPath, "utf-8"));
4925
+ if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
4926
+ if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
4927
+ return {
4928
+ pid: Number(parsed.pid),
4929
+ hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
4930
+ startedAt: parsed.startedAt,
4931
+ operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
4932
+ token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
4933
+ };
4934
+ } catch {
4935
+ return null;
4936
+ }
4937
+ }
4938
+ function getOwnerLiveness(owner) {
4939
+ if (owner.hostname !== os4.hostname()) return "unknown";
4940
+ try {
4941
+ process.kill(owner.pid, 0);
4942
+ return "alive";
4943
+ } catch (error) {
4944
+ const code = getErrorCode(error);
4945
+ if (code === "ESRCH") return "dead";
4946
+ if (code === "EPERM") return "alive";
4947
+ return "unknown";
4948
+ }
4949
+ }
4950
+ function sameOwner(left, right) {
4951
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
4952
+ }
4953
+ function sameReclaimOwner(left, right) {
4954
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
4955
+ }
4956
+ function publishJsonDirectory(finalPath, value) {
4957
+ const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
4958
+ (0, import_fs7.mkdirSync)(candidatePath, { mode: 448 });
4959
+ try {
4960
+ (0, import_fs7.writeFileSync)(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4961
+ encoding: "utf-8",
4962
+ flag: "wx",
4963
+ mode: 384
4964
+ });
4965
+ if ((0, import_fs7.existsSync)(finalPath)) return false;
4966
+ try {
4967
+ (0, import_fs7.renameSync)(candidatePath, finalPath);
4968
+ return true;
4969
+ } catch (error) {
4970
+ if ((0, import_fs7.existsSync)(finalPath)) return false;
4971
+ throw error;
4972
+ }
4973
+ } finally {
4974
+ if ((0, import_fs7.existsSync)(candidatePath)) (0, import_fs7.rmSync)(candidatePath, { recursive: true, force: true });
4975
+ }
4976
+ }
4977
+ function createOwner(operation) {
4978
+ return {
4979
+ pid: process.pid,
4980
+ hostname: os4.hostname(),
4981
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4982
+ operation,
4983
+ token: (0, import_crypto.randomUUID)()
4984
+ };
4985
+ }
4986
+ function recoveryMarkerPath(indexPath, owner) {
4987
+ return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4988
+ }
4989
+ function publishRecoveryMarker(indexPath, owner) {
4990
+ const markerPath = recoveryMarkerPath(indexPath, owner);
4991
+ if (!publishJsonDirectory(markerPath, owner)) {
4992
+ const markerOwner = readRecoveryOwner(markerPath);
4993
+ if (!markerOwner || !sameOwner(markerOwner, owner)) {
4994
+ throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
4995
+ }
4996
+ }
4997
+ return markerPath;
4998
+ }
4999
+ function getPendingRecoveries(indexPath) {
5000
+ const recoveries = [];
5001
+ const markerNames = (0, import_fs7.readdirSync)(indexPath).filter((name) => {
5002
+ if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
5003
+ return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
5004
+ }).sort();
5005
+ for (const markerName of markerNames) {
5006
+ const markerPath = path12.join(indexPath, markerName);
5007
+ const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
5008
+ let markerStats;
5009
+ try {
5010
+ markerStats = (0, import_fs7.lstatSync)(markerPath);
5011
+ } catch (error) {
5012
+ if (getErrorCode(error) === "ENOENT") continue;
5013
+ throw error;
5014
+ }
5015
+ if (!markerStats.isDirectory()) {
5016
+ throw new IndexLockContentionError(markerPath, null, "unknown-owner");
5017
+ }
5018
+ const owner = readRecoveryOwner(markerPath);
5019
+ if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
5020
+ throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
5021
+ }
5022
+ recoveries.push({ owner, markerPath });
5023
+ }
5024
+ recoveries.sort((left, right) => {
5025
+ const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
5026
+ return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
5027
+ });
5028
+ return recoveries;
5029
+ }
5030
+ function cleanupDeadPublicationCandidates(indexPath) {
5031
+ const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
5032
+ for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
5033
+ const match = candidatePattern.exec(entry);
5034
+ if (!match) continue;
5035
+ const pid = Number(match[1]);
5036
+ if (!Number.isInteger(pid) || pid <= 0) continue;
5037
+ if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
5038
+ (0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
5039
+ }
5040
+ }
5041
+ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5042
+ const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5043
+ const marker = readReclaimOwner(markerPath);
5044
+ if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
5045
+ return false;
5046
+ }
5047
+ const currentOwner = readDirectoryOwner(lockPath);
5048
+ if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5049
+ return false;
5050
+ }
5051
+ const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
5052
+ try {
5053
+ (0, import_fs7.renameSync)(markerPath, claimedMarkerPath);
5054
+ } catch (error) {
5055
+ if (getErrorCode(error) === "ENOENT") return false;
5056
+ throw error;
5057
+ }
5058
+ const claimedMarker = readReclaimOwner(claimedMarkerPath);
5059
+ const ownerAfterClaim = readDirectoryOwner(lockPath);
5060
+ if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
5061
+ if (!(0, import_fs7.existsSync)(markerPath) && (0, import_fs7.existsSync)(claimedMarkerPath)) {
5062
+ (0, import_fs7.renameSync)(claimedMarkerPath, markerPath);
5063
+ }
5064
+ return false;
5065
+ }
5066
+ (0, import_fs7.rmSync)(claimedMarkerPath, { recursive: true, force: true });
5067
+ return true;
5068
+ }
5069
+ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5070
+ const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5071
+ const reclaimOwner = {
5072
+ pid: process.pid,
5073
+ hostname: os4.hostname(),
5074
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5075
+ token: (0, import_crypto.randomUUID)(),
5076
+ expectedOwnerToken: expectedOwner.token
5077
+ };
5078
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5079
+ if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
5080
+ if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
5081
+ return false;
5082
+ }
5083
+ try {
5084
+ const currentReclaimer = readReclaimOwner(reclaimPath);
5085
+ const currentOwner = readDirectoryOwner(lockPath);
5086
+ if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5087
+ return false;
5088
+ }
5089
+ publishRecoveryMarker(indexPath, expectedOwner);
5090
+ const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
5091
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
5092
+ if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
5093
+ return false;
5094
+ }
5095
+ const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
5096
+ (0, import_fs7.renameSync)(lockPath, quarantinePath);
5097
+ (0, import_fs7.rmSync)(quarantinePath, { recursive: true, force: true });
5098
+ return true;
5099
+ } catch (error) {
5100
+ if (getErrorCode(error) === "ENOENT") return false;
5101
+ throw error;
5102
+ }
5103
+ }
5104
+ var IndexLockContentionError = class extends Error {
5105
+ constructor(lockPath, owner, reason) {
5106
+ const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
5107
+ super(`Index mutation already in progress: ${ownerDescription}`);
5108
+ this.lockPath = lockPath;
5109
+ this.owner = owner;
5110
+ this.reason = reason;
5111
+ this.name = "IndexLockContentionError";
5112
+ }
5113
+ lockPath;
5114
+ owner;
5115
+ reason;
5116
+ code = "INDEX_BUSY";
5117
+ };
5118
+ function isIndexLockContentionError(error) {
5119
+ return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
5120
+ }
5121
+ function acquireIndexLock(indexPath, operation) {
5122
+ (0, import_fs7.mkdirSync)(indexPath, { recursive: true });
5123
+ const canonicalIndexPath = import_fs7.realpathSync.native(indexPath);
5124
+ const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
5125
+ cleanupDeadPublicationCandidates(canonicalIndexPath);
5126
+ for (let attempt = 0; attempt < 6; attempt += 1) {
5127
+ const owner = createOwner(operation);
5128
+ if (publishJsonDirectory(lockPath, owner)) {
5129
+ const lease = {
5130
+ canonicalIndexPath,
5131
+ lockPath,
5132
+ owner,
5133
+ recoveries: []
5134
+ };
5135
+ try {
5136
+ lease.recoveries = getPendingRecoveries(canonicalIndexPath);
5137
+ return lease;
5138
+ } catch (error) {
5139
+ releaseIndexLock(lease);
5140
+ throw error;
5141
+ }
5142
+ }
5143
+ let stats;
5144
+ try {
5145
+ stats = (0, import_fs7.lstatSync)(lockPath);
5146
+ } catch (error) {
5147
+ if (getErrorCode(error) === "ENOENT") continue;
5148
+ throw error;
5149
+ }
5150
+ if (!stats.isDirectory()) {
5151
+ const legacyOwner = readLegacyOwner(lockPath);
5152
+ throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
5153
+ }
5154
+ const existingOwner = readDirectoryOwner(lockPath);
5155
+ if (!existingOwner) {
5156
+ throw new IndexLockContentionError(lockPath, null, "unknown-owner");
5157
+ }
5158
+ const liveness = getOwnerLiveness(existingOwner);
5159
+ if (liveness !== "dead") {
5160
+ throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
5161
+ }
5162
+ if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
5163
+ throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
5164
+ }
5165
+ }
5166
+ throw new IndexLockContentionError(lockPath, null, "reclaiming");
5167
+ }
5168
+ function releaseIndexLock(lease) {
5169
+ const currentOwner = readDirectoryOwner(lease.lockPath);
5170
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
5171
+ const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
5172
+ try {
5173
+ retryTransientFilesystemOperation(() => (0, import_fs7.renameSync)(lease.lockPath, releasePath));
5174
+ } catch (error) {
5175
+ if (getErrorCode(error) === "ENOENT") return false;
5176
+ throw error;
5177
+ }
5178
+ const claimedOwner = readDirectoryOwner(releasePath);
5179
+ if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
5180
+ if (!(0, import_fs7.existsSync)(lease.lockPath) && (0, import_fs7.existsSync)(releasePath)) {
5181
+ (0, import_fs7.renameSync)(releasePath, lease.lockPath);
5182
+ }
5183
+ return false;
5184
+ }
5185
+ try {
5186
+ retryTransientFilesystemOperation(() => (0, import_fs7.rmSync)(releasePath, { recursive: true, force: true }));
5187
+ } catch (error) {
5188
+ console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
5189
+ }
5190
+ return true;
5191
+ }
5192
+ async function withIndexLock(indexPath, operation, callback, options = {}) {
5193
+ const lease = acquireIndexLock(indexPath, operation);
5194
+ let result;
5195
+ let callbackError;
5196
+ let callbackFailed = false;
5197
+ try {
5198
+ result = await callback(lease);
5199
+ } catch (error) {
5200
+ callbackFailed = true;
5201
+ callbackError = error;
5202
+ }
5203
+ if (!callbackFailed && options.completeRecoveries !== false) {
5204
+ try {
5205
+ completeLeaseRecovery(lease);
5206
+ } catch (error) {
5207
+ callbackFailed = true;
5208
+ callbackError = error;
5209
+ }
5210
+ }
5211
+ let releaseError;
5212
+ try {
5213
+ if (!releaseIndexLock(lease)) {
5214
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5215
+ }
5216
+ } catch (error) {
5217
+ releaseError = error;
5218
+ }
5219
+ if (releaseError !== void 0) {
5220
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5221
+ throw releaseError;
5222
+ }
5223
+ if (callbackFailed) throw callbackError;
5224
+ return result;
5225
+ }
5226
+ function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5227
+ if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5228
+ temporaryCounter += 1;
5229
+ return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
5230
+ }
5231
+ function removeLeaseTemporaryPath(temporaryPath) {
5232
+ if ((0, import_fs7.existsSync)(temporaryPath)) (0, import_fs7.rmSync)(temporaryPath, { recursive: true, force: true });
5233
+ }
5234
+ function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
5235
+ for (const targetPath of backupTargets) {
5236
+ const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
5237
+ if (!(0, import_fs7.existsSync)(backupPath)) continue;
5238
+ if ((0, import_fs7.existsSync)(targetPath)) (0, import_fs7.rmSync)(targetPath, { recursive: true, force: true });
5239
+ (0, import_fs7.renameSync)(backupPath, targetPath);
5240
+ }
5241
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
5242
+ for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
5243
+ if (!entry.includes(temporaryOwnerMarker)) continue;
5244
+ (0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
5245
+ }
5246
+ }
5247
+ function completeLeaseRecovery(lease) {
5248
+ for (const recovery of lease.recoveries) {
5249
+ const markerOwner = readRecoveryOwner(recovery.markerPath);
5250
+ if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
5251
+ throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
5252
+ }
5253
+ }
5254
+ for (const recovery of lease.recoveries) {
5255
+ (0, import_fs7.rmSync)(recovery.markerPath, { recursive: true, force: true });
5256
+ }
5257
+ }
5258
+
4760
5259
  // src/indexer/index.ts
4761
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
5260
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4762
5261
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
4763
5262
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4764
5263
  "function_declaration",
@@ -4838,6 +5337,46 @@ function isSqliteCorruptionError(error) {
4838
5337
  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");
4839
5338
  }
4840
5339
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5340
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
5341
+ function metadataFromBlame(blame) {
5342
+ if (!blame) {
5343
+ return {};
5344
+ }
5345
+ return {
5346
+ blameSha: blame.sha,
5347
+ blameAuthor: blame.author,
5348
+ blameAuthorEmail: blame.authorEmail,
5349
+ blameCommittedAt: blame.committedAt,
5350
+ blameSummary: blame.summary
5351
+ };
5352
+ }
5353
+ function blameFromChunkData(chunk) {
5354
+ if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
5355
+ return void 0;
5356
+ }
5357
+ return {
5358
+ sha: chunk.blameSha,
5359
+ author: chunk.blameAuthor,
5360
+ authorEmail: chunk.blameAuthorEmail,
5361
+ committedAt: chunk.blameCommittedAt,
5362
+ summary: chunk.blameSummary
5363
+ };
5364
+ }
5365
+ function blameFromMetadata(metadata) {
5366
+ if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
5367
+ return void 0;
5368
+ }
5369
+ return {
5370
+ sha: metadata.blameSha,
5371
+ author: metadata.blameAuthor,
5372
+ authorEmail: metadata.blameAuthorEmail,
5373
+ committedAt: metadata.blameCommittedAt,
5374
+ summary: metadata.blameSummary
5375
+ };
5376
+ }
5377
+ function hasBlameMetadata(metadata) {
5378
+ return blameFromMetadata(metadata) !== void 0;
5379
+ }
4841
5380
  var INDEX_METADATA_VERSION = "1";
4842
5381
  var EMBEDDING_STRATEGY_VERSION = "2";
4843
5382
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
@@ -4881,9 +5420,9 @@ function normalizePendingChunk(rawChunk, maxChunkTokens) {
4881
5420
  };
4882
5421
  const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
4883
5422
  texts.push(
4884
- ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text2) => ({
4885
- text: text2,
4886
- tokenCount: estimateTokens(text2)
5423
+ ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text3) => ({
5424
+ text: text3,
5425
+ tokenCount: estimateTokens(text3)
4887
5426
  }))
4888
5427
  );
4889
5428
  } else {
@@ -4996,9 +5535,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
4996
5535
  return true;
4997
5536
  }
4998
5537
  function isPathWithinRoot(filePath, rootPath) {
4999
- const normalizedFilePath = path11.resolve(filePath);
5000
- const normalizedRoot = path11.resolve(rootPath);
5001
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5538
+ const normalizedFilePath = path13.resolve(filePath);
5539
+ const normalizedRoot = path13.resolve(rootPath);
5540
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5002
5541
  }
5003
5542
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5004
5543
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5114,11 +5653,11 @@ function setBoundedCache(cache, key, value) {
5114
5653
  }
5115
5654
  cache.set(key, value);
5116
5655
  }
5117
- function tokenizeTextForRanking(text2) {
5118
- if (!text2) {
5656
+ function tokenizeTextForRanking(text3) {
5657
+ if (!text3) {
5119
5658
  return /* @__PURE__ */ new Set();
5120
5659
  }
5121
- const lowered = text2.toLowerCase();
5660
+ const lowered = text3.toLowerCase();
5122
5661
  const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
5123
5662
  if (cache) {
5124
5663
  return cache;
@@ -5757,7 +6296,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
5757
6296
  chunkType,
5758
6297
  name: chunk.name ?? void 0,
5759
6298
  language: chunk.language,
5760
- hash: chunk.contentHash
6299
+ hash: chunk.contentHash,
6300
+ ...metadataFromBlame(blameFromChunkData(chunk))
5761
6301
  };
5762
6302
  const baselineScore = existing?.score ?? 0.5;
5763
6303
  candidateUnion.set(chunk.chunkId, {
@@ -5841,7 +6381,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
5841
6381
  chunkType,
5842
6382
  name: chunk.name ?? void 0,
5843
6383
  language: chunk.language,
5844
- hash: chunk.contentHash
6384
+ hash: chunk.contentHash,
6385
+ ...metadataFromBlame(blameFromChunkData(chunk))
5845
6386
  }
5846
6387
  });
5847
6388
  }
@@ -6010,6 +6551,21 @@ function matchesSearchFilters(candidate, options, minScore) {
6010
6551
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
6011
6552
  return false;
6012
6553
  }
6554
+ if (options?.blameAuthor) {
6555
+ const author = options.blameAuthor.toLowerCase();
6556
+ const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
6557
+ const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
6558
+ if (candidateAuthor !== author && candidateEmail !== author) return false;
6559
+ }
6560
+ if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
6561
+ return false;
6562
+ }
6563
+ if (options?.blameSince) {
6564
+ const sinceMs = Date.parse(options.blameSince);
6565
+ if (Number.isNaN(sinceMs)) return false;
6566
+ const committedAt = candidate.metadata.blameCommittedAt;
6567
+ if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
6568
+ }
6013
6569
  return true;
6014
6570
  }
6015
6571
  function unionCandidates(semanticCandidates, keywordCandidates) {
@@ -6047,26 +6603,133 @@ var Indexer = class {
6047
6603
  queryCacheTtlMs = 5 * 60 * 1e3;
6048
6604
  querySimilarityThreshold = 0.85;
6049
6605
  indexCompatibility = null;
6050
- indexingLockPath = "";
6051
- constructor(projectRoot2, config, host = "opencode") {
6052
- this.projectRoot = projectRoot2;
6606
+ activeIndexLease = null;
6607
+ initializationPromise = null;
6608
+ initializationMode = "none";
6609
+ readIssues = [];
6610
+ retiredDatabases = [];
6611
+ readerArtifactFingerprint = null;
6612
+ writerArtifactFingerprint = null;
6613
+ readerArtifactRetryAfter = /* @__PURE__ */ new Map();
6614
+ constructor(projectRoot3, config, host = "opencode") {
6615
+ this.projectRoot = projectRoot3;
6053
6616
  this.config = config;
6054
6617
  this.host = host;
6055
6618
  this.indexPath = this.getIndexPath();
6056
- this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6057
- this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6058
- this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
6619
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6620
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6059
6621
  this.logger = initializeLogger(config.debug);
6060
6622
  }
6061
6623
  getIndexPath() {
6062
6624
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6063
6625
  }
6626
+ isLocalProjectIndexPath() {
6627
+ const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6628
+ if (this.host !== "opencode") {
6629
+ localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6630
+ }
6631
+ return localProjectIndexPaths.some((localPath) => {
6632
+ if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
6633
+ return path13.resolve(this.indexPath) === path13.resolve(localPath);
6634
+ }
6635
+ const indexStats = (0, import_fs8.statSync)(this.indexPath);
6636
+ const localStats = (0, import_fs8.statSync)(localPath);
6637
+ return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6638
+ });
6639
+ }
6640
+ resetLoadedIndexState(retireDatabase = false) {
6641
+ if (this.database) {
6642
+ if (retireDatabase) {
6643
+ this.retiredDatabases.push(this.database);
6644
+ } else {
6645
+ this.database.close();
6646
+ }
6647
+ }
6648
+ this.store = null;
6649
+ this.invertedIndex = null;
6650
+ this.database = null;
6651
+ this.provider = null;
6652
+ this.configuredProviderInfo = null;
6653
+ this.reranker = null;
6654
+ this.indexCompatibility = null;
6655
+ this.initializationMode = "none";
6656
+ this.readIssues = [];
6657
+ this.readerArtifactFingerprint = null;
6658
+ this.writerArtifactFingerprint = null;
6659
+ this.readerArtifactRetryAfter.clear();
6660
+ this.fileHashCache.clear();
6661
+ }
6662
+ refreshLoadedIndexState() {
6663
+ if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
6664
+ this.store.load();
6665
+ this.invertedIndex.load();
6666
+ this.fileHashCache.clear();
6667
+ this.loadFileHashCache();
6668
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
6669
+ this.readIssues = [];
6670
+ this.readerArtifactRetryAfter.clear();
6671
+ }
6672
+ async withIndexMutationLease(operation, callback) {
6673
+ const lease = acquireIndexLock(this.indexPath, operation);
6674
+ this.indexPath = lease.canonicalIndexPath;
6675
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6676
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6677
+ this.activeIndexLease = lease;
6678
+ let result;
6679
+ let callbackError;
6680
+ let callbackFailed = false;
6681
+ try {
6682
+ result = await callback(lease.recoveries.map(({ owner }) => owner));
6683
+ } catch (error) {
6684
+ callbackFailed = true;
6685
+ callbackError = error;
6686
+ }
6687
+ if (!callbackFailed) {
6688
+ try {
6689
+ completeLeaseRecovery(lease);
6690
+ this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
6691
+ } catch (error) {
6692
+ callbackFailed = true;
6693
+ callbackError = error;
6694
+ }
6695
+ }
6696
+ let releaseError;
6697
+ try {
6698
+ if (!releaseIndexLock(lease)) {
6699
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
6700
+ this.writerArtifactFingerprint = null;
6701
+ if (this.activeIndexLease?.owner.token === lease.owner.token) {
6702
+ this.activeIndexLease = null;
6703
+ }
6704
+ } else if (this.activeIndexLease?.owner.token === lease.owner.token) {
6705
+ this.activeIndexLease = null;
6706
+ }
6707
+ } catch (error) {
6708
+ releaseError = error;
6709
+ this.writerArtifactFingerprint = null;
6710
+ if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6711
+ this.activeIndexLease = null;
6712
+ }
6713
+ }
6714
+ if (releaseError !== void 0) {
6715
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
6716
+ throw releaseError;
6717
+ }
6718
+ if (callbackFailed) throw callbackError;
6719
+ return result;
6720
+ }
6721
+ requireActiveLease() {
6722
+ if (!this.activeIndexLease) {
6723
+ throw new Error("Index mutation attempted without an active interprocess lease");
6724
+ }
6725
+ return this.activeIndexLease;
6726
+ }
6064
6727
  loadFileHashCache() {
6065
- if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
6728
+ if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
6066
6729
  return;
6067
6730
  }
6068
6731
  try {
6069
- const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
6732
+ const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
6070
6733
  const parsed = JSON.parse(data);
6071
6734
  this.fileHashCache = new Map(Object.entries(parsed));
6072
6735
  } catch (error) {
@@ -6086,15 +6749,26 @@ var Indexer = class {
6086
6749
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6087
6750
  }
6088
6751
  atomicWriteSync(targetPath, data) {
6089
- const tempPath = `${targetPath}.tmp`;
6090
- (0, import_fs7.mkdirSync)(path11.dirname(targetPath), { recursive: true });
6091
- (0, import_fs7.writeFileSync)(tempPath, data);
6092
- (0, import_fs7.renameSync)(tempPath, targetPath);
6752
+ const lease = this.requireActiveLease();
6753
+ const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6754
+ (0, import_fs8.mkdirSync)(path13.dirname(targetPath), { recursive: true });
6755
+ try {
6756
+ (0, import_fs8.writeFileSync)(tempPath, data);
6757
+ (0, import_fs8.renameSync)(tempPath, targetPath);
6758
+ } finally {
6759
+ removeLeaseTemporaryPath(tempPath);
6760
+ }
6761
+ }
6762
+ saveInvertedIndex(invertedIndex) {
6763
+ this.atomicWriteSync(
6764
+ path13.join(this.indexPath, "inverted-index.json"),
6765
+ invertedIndex.serialize()
6766
+ );
6093
6767
  }
6094
6768
  getScopedRoots() {
6095
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6769
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6096
6770
  for (const kbRoot of this.config.knowledgeBases) {
6097
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6771
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
6098
6772
  }
6099
6773
  return Array.from(roots);
6100
6774
  }
@@ -6103,29 +6777,29 @@ var Indexer = class {
6103
6777
  if (this.config.scope !== "global") {
6104
6778
  return branchName;
6105
6779
  }
6106
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6780
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6107
6781
  return `${projectHash}:${branchName}`;
6108
6782
  }
6109
6783
  getBranchCatalogKeyFor(branchName) {
6110
6784
  if (this.config.scope !== "global") {
6111
6785
  return branchName;
6112
6786
  }
6113
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6787
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6114
6788
  return `${projectHash}:${branchName}`;
6115
6789
  }
6116
6790
  getLegacyBranchCatalogKey() {
6117
6791
  return this.currentBranch || "default";
6118
6792
  }
6119
6793
  getLegacyMigrationMetadataKey() {
6120
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6794
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6121
6795
  return `index.globalBranchMigration.${projectHash}`;
6122
6796
  }
6123
6797
  getProjectEmbeddingStrategyMetadataKey() {
6124
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6798
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6125
6799
  return `index.embeddingStrategyVersion.${projectHash}`;
6126
6800
  }
6127
6801
  getProjectForceReembedMetadataKey() {
6128
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6802
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6129
6803
  return `index.forceReembed.${projectHash}`;
6130
6804
  }
6131
6805
  hasProjectForceReembedPending() {
@@ -6219,7 +6893,7 @@ var Indexer = class {
6219
6893
  if (!this.database) {
6220
6894
  return { chunkIds, symbolIds };
6221
6895
  }
6222
- const projectRootPath = path11.resolve(this.projectRoot);
6896
+ const projectRootPath = path13.resolve(this.projectRoot);
6223
6897
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6224
6898
  ...Array.from(this.fileHashCache.keys()).filter(
6225
6899
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6242,7 +6916,7 @@ var Indexer = class {
6242
6916
  if (this.config.scope !== "global") {
6243
6917
  return this.getBranchCatalogCleanupKeys();
6244
6918
  }
6245
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6919
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6246
6920
  const keys = /* @__PURE__ */ new Set();
6247
6921
  const projectChunkIdSet = new Set(projectChunkIds);
6248
6922
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6326,7 +7000,7 @@ var Indexer = class {
6326
7000
  if (!this.database || this.config.scope !== "global") {
6327
7001
  return false;
6328
7002
  }
6329
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7003
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6330
7004
  const roots = this.getScopedRoots();
6331
7005
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6332
7006
  return this.database.getAllBranches().some(
@@ -6360,7 +7034,7 @@ var Indexer = class {
6360
7034
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6361
7035
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6362
7036
  ]);
6363
- const projectRootPath = path11.resolve(this.projectRoot);
7037
+ const projectRootPath = path13.resolve(this.projectRoot);
6364
7038
  const projectLocalFilePaths = new Set(
6365
7039
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6366
7040
  );
@@ -6422,34 +7096,27 @@ var Indexer = class {
6422
7096
  database.gcOrphanEmbeddings();
6423
7097
  database.gcOrphanChunks();
6424
7098
  store.save();
6425
- invertedIndex.save();
7099
+ this.saveInvertedIndex(invertedIndex);
6426
7100
  return {
6427
7101
  removedChunkIds: removedChunkIdList,
6428
7102
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6429
7103
  };
6430
7104
  }
6431
- checkForInterruptedIndexing() {
6432
- return (0, import_fs7.existsSync)(this.indexingLockPath);
6433
- }
6434
- acquireIndexingLock() {
6435
- const lockData = {
6436
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6437
- pid: process.pid
6438
- };
6439
- (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
6440
- }
6441
- releaseIndexingLock() {
6442
- if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
6443
- (0, import_fs7.unlinkSync)(this.indexingLockPath);
7105
+ async recoverFromInterruptedIndexingUnlocked(owners) {
7106
+ for (const owner of owners) {
7107
+ this.logger.warn("Detected interrupted indexing session, recovering...", {
7108
+ pid: owner.pid,
7109
+ hostname: owner.hostname,
7110
+ operation: owner.operation,
7111
+ startedAt: owner.startedAt
7112
+ });
6444
7113
  }
6445
- }
6446
- async recoverFromInterruptedIndexing() {
6447
- this.logger.warn("Detected interrupted indexing session, recovering...");
6448
- if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
6449
- (0, import_fs7.unlinkSync)(this.fileHashCachePath);
7114
+ if (this.config.scope === "global") {
7115
+ if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
7116
+ (0, import_fs8.unlinkSync)(this.fileHashCachePath);
7117
+ }
7118
+ await this.healthCheckUnlocked();
6450
7119
  }
6451
- await this.healthCheck();
6452
- this.releaseIndexingLock();
6453
7120
  this.logger.info("Recovery complete, next index will re-process all files");
6454
7121
  }
6455
7122
  loadFailedBatches(maxChunkTokens) {
@@ -6465,10 +7132,10 @@ var Indexer = class {
6465
7132
  }
6466
7133
  }
6467
7134
  loadSerializedFailedBatches() {
6468
- if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
7135
+ if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
6469
7136
  return [];
6470
7137
  }
6471
- const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
7138
+ const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
6472
7139
  const parsed = JSON.parse(data);
6473
7140
  return parsed.map((batch) => {
6474
7141
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6485,15 +7152,15 @@ var Indexer = class {
6485
7152
  }
6486
7153
  saveFailedBatches(batches) {
6487
7154
  if (batches.length === 0) {
6488
- if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
7155
+ if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
6489
7156
  try {
6490
- (0, import_fs7.unlinkSync)(this.failedBatchesPath);
7157
+ (0, import_fs8.unlinkSync)(this.failedBatchesPath);
6491
7158
  } catch {
6492
7159
  }
6493
7160
  }
6494
7161
  return;
6495
7162
  }
6496
- (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7163
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6497
7164
  }
6498
7165
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6499
7166
  const retryableById = /* @__PURE__ */ new Map();
@@ -6673,7 +7340,7 @@ var Indexer = class {
6673
7340
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
6674
7341
  parts.push(`intent_hint: ${intent}`);
6675
7342
  try {
6676
- const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
7343
+ const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
6677
7344
  const lines = fileContent.split("\n");
6678
7345
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
6679
7346
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -6687,29 +7354,245 @@ var Indexer = class {
6687
7354
  return parts.join("\n");
6688
7355
  }
6689
7356
  async initialize() {
6690
- if (this.config.embeddingProvider === "custom") {
6691
- if (!this.config.customProvider) {
6692
- throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
7357
+ if (this.initializationPromise) {
7358
+ await this.initializationPromise;
7359
+ }
7360
+ if (this.isInitializedFor("reader")) {
7361
+ return;
7362
+ }
7363
+ await this.initializeOnce("reader", [], { skipAutoGc: true });
7364
+ }
7365
+ async initializeOnce(mode, recoveredOwners, options) {
7366
+ if (this.initializationPromise) {
7367
+ await this.initializationPromise;
7368
+ if (this.isInitializedFor(mode)) {
7369
+ return;
6693
7370
  }
6694
- this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
6695
- } else if (this.config.embeddingProvider === "auto") {
6696
- this.configuredProviderInfo = await tryDetectProvider();
6697
- } else {
6698
- this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
7371
+ return this.initializeOnce(mode, recoveredOwners, options);
6699
7372
  }
6700
- if (!this.configuredProviderInfo) {
6701
- throw new Error(
6702
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
6703
- );
7373
+ if (this.isInitializedFor(mode)) {
7374
+ return;
6704
7375
  }
6705
- this.logger.info("Initializing indexer", {
6706
- provider: this.configuredProviderInfo.provider,
6707
- model: this.configuredProviderInfo.modelInfo.model,
6708
- scope: this.config.scope,
6709
- rerankerEnabled: this.config.reranker?.enabled ?? false
7376
+ const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
7377
+ this.resetLoadedIndexState();
7378
+ throw error;
7379
+ }).finally(() => {
7380
+ if (this.initializationPromise === initialization) {
7381
+ this.initializationPromise = null;
7382
+ }
6710
7383
  });
6711
- this.provider = createEmbeddingProvider(this.configuredProviderInfo);
6712
- if (this.config.reranker?.enabled) {
7384
+ this.initializationPromise = initialization;
7385
+ await initialization;
7386
+ }
7387
+ isInitializedFor(mode) {
7388
+ const hasState = Boolean(
7389
+ this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
7390
+ );
7391
+ if (!hasState) {
7392
+ return false;
7393
+ }
7394
+ return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
7395
+ }
7396
+ recordReadIssue(component, message, error) {
7397
+ this.readIssues.push(this.createReadIssue(component, message));
7398
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7399
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7400
+ }
7401
+ createReadIssue(component, message) {
7402
+ return {
7403
+ component,
7404
+ message,
7405
+ blocking: component !== "keyword"
7406
+ };
7407
+ }
7408
+ getVectorReadIssueMessage() {
7409
+ if (this.config.scope === "global") {
7410
+ return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
7411
+ }
7412
+ if (!this.isLocalProjectIndexPath()) {
7413
+ 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.";
7414
+ }
7415
+ 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.";
7416
+ }
7417
+ getKeywordReadIssueMessage() {
7418
+ if (this.config.scope === "global") {
7419
+ 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.";
7420
+ }
7421
+ if (!this.isLocalProjectIndexPath()) {
7422
+ 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.";
7423
+ }
7424
+ 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.";
7425
+ }
7426
+ getDatabaseReadIssueMessage() {
7427
+ if (this.config.scope === "global") {
7428
+ return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
7429
+ }
7430
+ if (!this.isLocalProjectIndexPath()) {
7431
+ 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.";
7432
+ }
7433
+ return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
7434
+ }
7435
+ getReaderFileFingerprint(filePath, identityOnly = false) {
7436
+ try {
7437
+ const stats = (0, import_fs8.statSync)(filePath);
7438
+ if (identityOnly) {
7439
+ return `${stats.dev}:${stats.ino}`;
7440
+ }
7441
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
7442
+ } catch (error) {
7443
+ return `unavailable:${getErrorMessage2(error)}`;
7444
+ }
7445
+ }
7446
+ captureReaderArtifactFingerprint() {
7447
+ const storePath = path13.join(this.indexPath, "vectors");
7448
+ return {
7449
+ vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7450
+ keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7451
+ database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7452
+ databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7453
+ };
7454
+ }
7455
+ refreshReaderArtifacts() {
7456
+ if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
7457
+ return;
7458
+ }
7459
+ const previousFingerprint = this.readerArtifactFingerprint;
7460
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7461
+ const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
7462
+ const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
7463
+ const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
7464
+ const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
7465
+ const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
7466
+ const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7467
+ if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
7468
+ return;
7469
+ }
7470
+ const setIssue = (component, message, error) => {
7471
+ if (!issues.has(component)) {
7472
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7473
+ }
7474
+ issues.set(component, this.createReadIssue(component, message));
7475
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7476
+ };
7477
+ const storePath = path13.join(this.indexPath, "vectors");
7478
+ const vectorMetadataPath = `${storePath}.meta.json`;
7479
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7480
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7481
+ if (vectorsChanged || retryDue("vectors")) {
7482
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7483
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7484
+ if (vectorStoreExists && vectorMetadataExists) {
7485
+ try {
7486
+ const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
7487
+ store.loadStrict();
7488
+ this.store = store;
7489
+ issues.delete("vectors");
7490
+ this.readerArtifactRetryAfter.delete("vectors");
7491
+ } catch (error) {
7492
+ setIssue("vectors", this.getVectorReadIssueMessage(), error);
7493
+ }
7494
+ } else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
7495
+ setIssue("vectors", this.getVectorReadIssueMessage());
7496
+ }
7497
+ }
7498
+ if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7499
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7500
+ try {
7501
+ const invertedIndex = new InvertedIndex(invertedIndexPath);
7502
+ invertedIndex.load();
7503
+ this.invertedIndex = invertedIndex;
7504
+ issues.delete("keyword");
7505
+ this.readerArtifactRetryAfter.delete("keyword");
7506
+ } catch (error) {
7507
+ setIssue("keyword", this.getKeywordReadIssueMessage(), error);
7508
+ }
7509
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
7510
+ setIssue("keyword", this.getKeywordReadIssueMessage());
7511
+ }
7512
+ }
7513
+ if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7514
+ if ((0, import_fs8.existsSync)(dbPath)) {
7515
+ try {
7516
+ const database = Database.openReadOnly(dbPath);
7517
+ if (this.database) {
7518
+ this.retiredDatabases.push(this.database);
7519
+ }
7520
+ this.database = database;
7521
+ issues.delete("database");
7522
+ this.readerArtifactRetryAfter.delete("database");
7523
+ } catch (error) {
7524
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7525
+ }
7526
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
7527
+ setIssue("database", this.getDatabaseReadIssueMessage());
7528
+ }
7529
+ }
7530
+ if (!issues.has("database")) {
7531
+ try {
7532
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7533
+ } catch (error) {
7534
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7535
+ }
7536
+ }
7537
+ this.readIssues = Array.from(issues.values());
7538
+ this.readerArtifactFingerprint = currentFingerprint;
7539
+ }
7540
+ refreshInactiveWriterArtifacts() {
7541
+ if (this.initializationMode !== "writer" || this.activeIndexLease) {
7542
+ return true;
7543
+ }
7544
+ const previousFingerprint = this.writerArtifactFingerprint;
7545
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7546
+ const retryDue = this.readIssues.some(
7547
+ (issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
7548
+ );
7549
+ const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7550
+ if (!artifactsChanged && !retryDue) {
7551
+ return true;
7552
+ }
7553
+ if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
7554
+ return false;
7555
+ }
7556
+ this.initializationMode = "reader";
7557
+ this.readerArtifactFingerprint = previousFingerprint;
7558
+ try {
7559
+ this.refreshReaderArtifacts();
7560
+ this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
7561
+ } finally {
7562
+ this.readerArtifactFingerprint = null;
7563
+ this.initializationMode = "writer";
7564
+ }
7565
+ return true;
7566
+ }
7567
+ async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
7568
+ if (mode === "writer") {
7569
+ this.requireActiveLease();
7570
+ }
7571
+ this.readIssues = [];
7572
+ this.readerArtifactRetryAfter.clear();
7573
+ if (this.config.embeddingProvider === "custom") {
7574
+ if (!this.config.customProvider) {
7575
+ throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
7576
+ }
7577
+ this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
7578
+ } else if (this.config.embeddingProvider === "auto") {
7579
+ this.configuredProviderInfo = await tryDetectProvider();
7580
+ } else {
7581
+ this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
7582
+ }
7583
+ if (!this.configuredProviderInfo) {
7584
+ throw new Error(
7585
+ "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
7586
+ );
7587
+ }
7588
+ this.logger.info("Initializing indexer", {
7589
+ provider: this.configuredProviderInfo.provider,
7590
+ model: this.configuredProviderInfo.modelInfo.model,
7591
+ scope: this.config.scope,
7592
+ rerankerEnabled: this.config.reranker?.enabled ?? false
7593
+ });
7594
+ this.provider = createEmbeddingProvider(this.configuredProviderInfo);
7595
+ if (this.config.reranker?.enabled) {
6713
7596
  this.reranker = createReranker(this.config.reranker);
6714
7597
  if (this.reranker.isAvailable()) {
6715
7598
  this.logger.info("Reranker initialized", {
@@ -6718,36 +7601,103 @@ var Indexer = class {
6718
7601
  });
6719
7602
  }
6720
7603
  }
6721
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6722
7604
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6723
- const storePath = path11.join(this.indexPath, "vectors");
6724
- this.store = new VectorStore(storePath, dimensions);
6725
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6726
- if ((0, import_fs7.existsSync)(indexFilePath)) {
6727
- this.store.load();
6728
- }
6729
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6730
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6731
- try {
6732
- this.invertedIndex.load();
6733
- } catch {
6734
- if ((0, import_fs7.existsSync)(invertedIndexPath)) {
6735
- await import_fs7.promises.unlink(invertedIndexPath);
7605
+ const storePath = path13.join(this.indexPath, "vectors");
7606
+ const vectorMetadataPath = `${storePath}.meta.json`;
7607
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7608
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7609
+ let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
7610
+ const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7611
+ if (mode === "writer") {
7612
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7613
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7614
+ throw new Error(
7615
+ "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
7616
+ );
7617
+ }
7618
+ for (const recoveredOwner of recoveredOwners) {
7619
+ recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
7620
+ storePath,
7621
+ `${storePath}.meta.json`
7622
+ ]);
7623
+ }
7624
+ if (recoveredOwners.length > 0 && this.config.scope === "project") {
7625
+ await this.resetLocalIndexArtifacts();
7626
+ }
7627
+ this.store = new VectorStore(storePath, dimensions);
7628
+ if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
7629
+ this.store.load();
6736
7630
  }
6737
7631
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6738
- }
6739
- const dbPath = path11.join(this.indexPath, "codebase.db");
6740
- let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6741
- try {
6742
- this.database = new Database(dbPath);
6743
- } catch (error) {
6744
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6745
- throw error;
7632
+ try {
7633
+ this.invertedIndex.load();
7634
+ } catch {
7635
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7636
+ await import_fs8.promises.unlink(invertedIndexPath);
7637
+ }
7638
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7639
+ }
7640
+ try {
7641
+ this.database = new Database(dbPath);
7642
+ } catch (error) {
7643
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7644
+ throw error;
7645
+ }
7646
+ this.store = new VectorStore(storePath, dimensions);
7647
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7648
+ this.database = new Database(dbPath);
7649
+ dbIsNew = true;
6746
7650
  }
7651
+ } else {
6747
7652
  this.store = new VectorStore(storePath, dimensions);
7653
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7654
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7655
+ const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7656
+ if (vectorStoreExists !== vectorMetadataExists) {
7657
+ this.recordReadIssue("vectors", vectorReadFailureMessage);
7658
+ } else if (vectorStoreExists) {
7659
+ try {
7660
+ this.store.loadStrict();
7661
+ } catch (error) {
7662
+ this.recordReadIssue("vectors", vectorReadFailureMessage, error);
7663
+ this.store = new VectorStore(storePath, dimensions);
7664
+ }
7665
+ }
6748
7666
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6749
- this.database = new Database(dbPath);
6750
- dbIsNew = true;
7667
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7668
+ try {
7669
+ this.invertedIndex.load();
7670
+ } catch (error) {
7671
+ this.recordReadIssue(
7672
+ "keyword",
7673
+ this.getKeywordReadIssueMessage(),
7674
+ error
7675
+ );
7676
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7677
+ }
7678
+ } else if (this.store.count() > 0) {
7679
+ this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7680
+ }
7681
+ if ((0, import_fs8.existsSync)(dbPath)) {
7682
+ try {
7683
+ this.database = Database.openReadOnly(dbPath);
7684
+ } catch (error) {
7685
+ this.recordReadIssue(
7686
+ "database",
7687
+ this.getDatabaseReadIssueMessage(),
7688
+ error
7689
+ );
7690
+ this.database = Database.createEmptyReadOnly();
7691
+ }
7692
+ } else {
7693
+ this.database = Database.createEmptyReadOnly();
7694
+ if (this.store.count() > 0) {
7695
+ this.recordReadIssue(
7696
+ "database",
7697
+ `Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
7698
+ );
7699
+ }
7700
+ }
6751
7701
  }
6752
7702
  if (isGitRepo(this.projectRoot)) {
6753
7703
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6761,10 +7711,10 @@ var Indexer = class {
6761
7711
  this.baseBranch = "default";
6762
7712
  this.logger.branch("debug", "Not a git repository, using default branch");
6763
7713
  }
6764
- if (this.checkForInterruptedIndexing()) {
6765
- await this.recoverFromInterruptedIndexing();
7714
+ if (mode === "writer" && recoveredOwners.length > 0) {
7715
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6766
7716
  }
6767
- if (dbIsNew && this.store.count() > 0) {
7717
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6768
7718
  this.migrateFromLegacyIndex();
6769
7719
  }
6770
7720
  this.loadFileHashCache();
@@ -6776,9 +7726,11 @@ var Indexer = class {
6776
7726
  configuredProviderInfo: this.configuredProviderInfo
6777
7727
  });
6778
7728
  }
6779
- if (this.config.indexing.autoGc) {
7729
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6780
7730
  await this.maybeRunAutoGc();
6781
7731
  }
7732
+ this.initializationMode = mode;
7733
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6782
7734
  }
6783
7735
  async maybeRunAutoGc() {
6784
7736
  if (!this.database) return;
@@ -6795,7 +7747,7 @@ var Indexer = class {
6795
7747
  }
6796
7748
  }
6797
7749
  if (shouldRunGc) {
6798
- const result = await this.healthCheck();
7750
+ const result = await this.healthCheckUnlocked();
6799
7751
  if (result.warning) {
6800
7752
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
6801
7753
  } else {
@@ -6817,7 +7769,7 @@ var Indexer = class {
6817
7769
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6818
7770
  return {
6819
7771
  resetCorruptedIndex: true,
6820
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7772
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
6821
7773
  };
6822
7774
  }
6823
7775
  throw error;
@@ -6832,28 +7784,29 @@ var Indexer = class {
6832
7784
  return;
6833
7785
  }
6834
7786
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6835
- const storeBasePath = path11.join(this.indexPath, "vectors");
6836
- const storeIndexPath = `${storeBasePath}.usearch`;
7787
+ const storeBasePath = path13.join(this.indexPath, "vectors");
7788
+ const storeIndexPath = storeBasePath;
6837
7789
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6838
- const backupIndexPath = `${storeIndexPath}.bak`;
6839
- const backupMetadataPath = `${storeMetadataPath}.bak`;
7790
+ const lease = this.requireActiveLease();
7791
+ const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
7792
+ const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
6840
7793
  let backedUpIndex = false;
6841
7794
  let backedUpMetadata = false;
6842
7795
  let rebuiltCount = 0;
6843
7796
  let skippedCount = 0;
6844
- if ((0, import_fs7.existsSync)(backupIndexPath)) {
6845
- (0, import_fs7.unlinkSync)(backupIndexPath);
7797
+ if ((0, import_fs8.existsSync)(backupIndexPath)) {
7798
+ (0, import_fs8.unlinkSync)(backupIndexPath);
6846
7799
  }
6847
- if ((0, import_fs7.existsSync)(backupMetadataPath)) {
6848
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7800
+ if ((0, import_fs8.existsSync)(backupMetadataPath)) {
7801
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
6849
7802
  }
6850
7803
  try {
6851
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
6852
- (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
7804
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7805
+ (0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
6853
7806
  backedUpIndex = true;
6854
7807
  }
6855
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
6856
- (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
7808
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7809
+ (0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
6857
7810
  backedUpMetadata = true;
6858
7811
  }
6859
7812
  store.clear();
@@ -6873,11 +7826,11 @@ var Indexer = class {
6873
7826
  rebuiltCount += 1;
6874
7827
  }
6875
7828
  store.save();
6876
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
6877
- (0, import_fs7.unlinkSync)(backupIndexPath);
7829
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7830
+ (0, import_fs8.unlinkSync)(backupIndexPath);
6878
7831
  }
6879
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
6880
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7832
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7833
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
6881
7834
  }
6882
7835
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
6883
7836
  excludedChunks: excludedSet.size,
@@ -6889,17 +7842,17 @@ var Indexer = class {
6889
7842
  store.clear();
6890
7843
  } catch {
6891
7844
  }
6892
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
6893
- (0, import_fs7.unlinkSync)(storeIndexPath);
7845
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7846
+ (0, import_fs8.unlinkSync)(storeIndexPath);
6894
7847
  }
6895
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
6896
- (0, import_fs7.unlinkSync)(storeMetadataPath);
7848
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7849
+ (0, import_fs8.unlinkSync)(storeMetadataPath);
6897
7850
  }
6898
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
6899
- (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
7851
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7852
+ (0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
6900
7853
  }
6901
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
6902
- (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
7854
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7855
+ (0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
6903
7856
  }
6904
7857
  if (backedUpIndex || backedUpMetadata) {
6905
7858
  store.load();
@@ -6913,11 +7866,37 @@ var Indexer = class {
6913
7866
  }
6914
7867
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
6915
7868
  }
7869
+ async resetLocalIndexArtifacts() {
7870
+ this.store = null;
7871
+ this.invertedIndex = null;
7872
+ this.database?.close();
7873
+ this.database = null;
7874
+ this.indexCompatibility = null;
7875
+ this.initializationMode = "none";
7876
+ this.readIssues = [];
7877
+ this.readerArtifactFingerprint = null;
7878
+ this.writerArtifactFingerprint = null;
7879
+ this.readerArtifactRetryAfter.clear();
7880
+ this.fileHashCache.clear();
7881
+ const resetPaths = [
7882
+ path13.join(this.indexPath, "codebase.db"),
7883
+ path13.join(this.indexPath, "codebase.db-shm"),
7884
+ path13.join(this.indexPath, "codebase.db-wal"),
7885
+ path13.join(this.indexPath, "vectors"),
7886
+ path13.join(this.indexPath, "vectors.usearch"),
7887
+ path13.join(this.indexPath, "vectors.meta.json"),
7888
+ path13.join(this.indexPath, "inverted-index.json"),
7889
+ path13.join(this.indexPath, "file-hashes.json"),
7890
+ path13.join(this.indexPath, "failed-batches.json")
7891
+ ];
7892
+ await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
7893
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7894
+ }
6916
7895
  async tryResetCorruptedIndex(stage, error) {
6917
7896
  if (!isSqliteCorruptionError(error)) {
6918
7897
  return false;
6919
7898
  }
6920
- const dbPath = path11.join(this.indexPath, "codebase.db");
7899
+ const dbPath = path13.join(this.indexPath, "codebase.db");
6921
7900
  const warning = this.getCorruptedIndexWarning(dbPath);
6922
7901
  const errorMessage = getErrorMessage2(error);
6923
7902
  if (this.config.scope === "global") {
@@ -6933,30 +7912,7 @@ var Indexer = class {
6933
7912
  dbPath,
6934
7913
  error: errorMessage
6935
7914
  });
6936
- this.store = null;
6937
- this.invertedIndex = null;
6938
- this.database?.close();
6939
- this.database = null;
6940
- this.indexCompatibility = null;
6941
- this.fileHashCache.clear();
6942
- const resetPaths = [
6943
- path11.join(this.indexPath, "codebase.db"),
6944
- path11.join(this.indexPath, "codebase.db-shm"),
6945
- path11.join(this.indexPath, "codebase.db-wal"),
6946
- path11.join(this.indexPath, "vectors.usearch"),
6947
- path11.join(this.indexPath, "inverted-index.json"),
6948
- path11.join(this.indexPath, "file-hashes.json"),
6949
- path11.join(this.indexPath, "failed-batches.json"),
6950
- path11.join(this.indexPath, "indexing.lock"),
6951
- path11.join(this.indexPath, "vectors")
6952
- ];
6953
- await Promise.all(resetPaths.map(async (targetPath) => {
6954
- try {
6955
- await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
6956
- } catch {
6957
- }
6958
- }));
6959
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
7915
+ await this.resetLocalIndexArtifacts();
6960
7916
  return true;
6961
7917
  }
6962
7918
  migrateFromLegacyIndex() {
@@ -7075,8 +8031,62 @@ var Indexer = class {
7075
8031
  return this.indexCompatibility;
7076
8032
  }
7077
8033
  async ensureInitialized() {
8034
+ let initializedReader = false;
8035
+ while (true) {
8036
+ if (this.initializationPromise) {
8037
+ await this.initializationPromise;
8038
+ }
8039
+ if (!this.isInitializedFor("reader")) {
8040
+ await this.initialize();
8041
+ initializedReader = true;
8042
+ continue;
8043
+ }
8044
+ if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
8045
+ if (!this.refreshInactiveWriterArtifacts()) {
8046
+ this.resetLoadedIndexState(true);
8047
+ await this.initialize();
8048
+ initializedReader = true;
8049
+ continue;
8050
+ }
8051
+ }
8052
+ if (this.initializationMode === "reader" && !initializedReader) {
8053
+ this.refreshReaderArtifacts();
8054
+ }
8055
+ const state = this.requireLoadedIndexState();
8056
+ return {
8057
+ ...state,
8058
+ readIssues: [...this.readIssues],
8059
+ compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
8060
+ };
8061
+ }
8062
+ }
8063
+ async ensureInitializedUnlocked(recoveredOwners = []) {
8064
+ this.requireActiveLease();
8065
+ if (this.initializationPromise) {
8066
+ await this.initializationPromise;
8067
+ }
8068
+ if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
8069
+ const retireReaderDatabase = this.initializationMode === "reader";
8070
+ this.resetLoadedIndexState(retireReaderDatabase);
8071
+ await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
8072
+ } else {
8073
+ this.refreshLoadedIndexState();
8074
+ }
8075
+ if (this.config.indexing.autoGc) {
8076
+ await this.maybeRunAutoGc();
8077
+ }
8078
+ return this.requireLoadedIndexState();
8079
+ }
8080
+ requireReadableComponents(readIssues, ...components) {
8081
+ const componentSet = new Set(components);
8082
+ const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
8083
+ if (issues.length > 0) {
8084
+ throw new Error(issues.map((issue) => issue.message).join(" "));
8085
+ }
8086
+ }
8087
+ requireLoadedIndexState() {
7078
8088
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7079
- await this.initialize();
8089
+ throw new Error("Index state is not initialized");
7080
8090
  }
7081
8091
  return {
7082
8092
  store: this.store,
@@ -7100,7 +8110,12 @@ var Indexer = class {
7100
8110
  return createCostEstimate(files, configuredProviderInfo);
7101
8111
  }
7102
8112
  async index(onProgress) {
7103
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8113
+ return this.withIndexMutationLease("index", async (recoveredOwners) => {
8114
+ return this.indexUnlocked(onProgress, recoveredOwners);
8115
+ });
8116
+ }
8117
+ async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
8118
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
7104
8119
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7105
8120
  const branchCatalogKey = this.getBranchCatalogKey();
7106
8121
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7110,7 +8125,6 @@ var Indexer = class {
7110
8125
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7111
8126
  );
7112
8127
  }
7113
- this.acquireIndexingLock();
7114
8128
  this.logger.recordIndexingStart();
7115
8129
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7116
8130
  const startTime = Date.now();
@@ -7161,7 +8175,7 @@ var Indexer = class {
7161
8175
  unchangedFilePaths.add(f.path);
7162
8176
  this.logger.recordCacheHit();
7163
8177
  } else {
7164
- const content = await import_fs7.promises.readFile(f.path, "utf-8");
8178
+ const content = await import_fs8.promises.readFile(f.path, "utf-8");
7165
8179
  changedFiles.push({ path: f.path, content, hash: currentHash });
7166
8180
  this.logger.recordCacheMiss();
7167
8181
  }
@@ -7185,6 +8199,7 @@ var Indexer = class {
7185
8199
  this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
7186
8200
  const existingChunks = /* @__PURE__ */ new Map();
7187
8201
  const existingChunksByFile = /* @__PURE__ */ new Map();
8202
+ const existingMetadataById = /* @__PURE__ */ new Map();
7188
8203
  for (const { key, metadata } of store.getAllMetadata()) {
7189
8204
  if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
7190
8205
  continue;
@@ -7193,6 +8208,7 @@ var Indexer = class {
7193
8208
  continue;
7194
8209
  }
7195
8210
  existingChunks.set(key, metadata.hash);
8211
+ existingMetadataById.set(key, metadata);
7196
8212
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7197
8213
  fileChunks.add(key);
7198
8214
  existingChunksByFile.set(metadata.filePath, fileChunks);
@@ -7200,6 +8216,8 @@ var Indexer = class {
7200
8216
  const currentChunkIds = /* @__PURE__ */ new Set();
7201
8217
  const currentFilePaths = /* @__PURE__ */ new Set();
7202
8218
  const pendingChunks = [];
8219
+ const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
8220
+ let backfilledBlameMetadata = false;
7203
8221
  for (const filePath of unchangedFilePaths) {
7204
8222
  currentFilePaths.add(filePath);
7205
8223
  const fileChunks = existingChunksByFile.get(filePath);
@@ -7210,10 +8228,52 @@ var Indexer = class {
7210
8228
  }
7211
8229
  }
7212
8230
  const chunkDataBatch = [];
8231
+ if (gitBlameEnabled) {
8232
+ const backfillItems = [];
8233
+ for (const chunkId of currentChunkIds) {
8234
+ const metadata = existingMetadataById.get(chunkId);
8235
+ if (!metadata || hasBlameMetadata(metadata)) {
8236
+ continue;
8237
+ }
8238
+ const chunk = database.getChunk(chunkId);
8239
+ if (!chunk) {
8240
+ continue;
8241
+ }
8242
+ const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
8243
+ const blameMetadata = metadataFromBlame(blame);
8244
+ if (!blameMetadata.blameSha) {
8245
+ continue;
8246
+ }
8247
+ chunkDataBatch.push({
8248
+ ...chunk,
8249
+ blameSha: blameMetadata.blameSha,
8250
+ blameAuthor: blameMetadata.blameAuthor,
8251
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8252
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8253
+ blameSummary: blameMetadata.blameSummary
8254
+ });
8255
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8256
+ if (!embeddingBuffer) {
8257
+ continue;
8258
+ }
8259
+ backfillItems.push({
8260
+ id: chunkId,
8261
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
8262
+ metadata: {
8263
+ ...metadata,
8264
+ ...blameMetadata
8265
+ }
8266
+ });
8267
+ }
8268
+ if (backfillItems.length > 0) {
8269
+ store.addBatch(backfillItems);
8270
+ backfilledBlameMetadata = true;
8271
+ }
8272
+ }
7213
8273
  for (const parsed of parsedFiles) {
7214
8274
  currentFilePaths.add(parsed.path);
7215
8275
  if (parsed.chunks.length === 0) {
7216
- const relativePath = path11.relative(this.projectRoot, parsed.path);
8276
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
7217
8277
  stats.parseFailures.push(relativePath);
7218
8278
  }
7219
8279
  let fileChunkCount = 0;
@@ -7234,6 +8294,10 @@ var Indexer = class {
7234
8294
  }
7235
8295
  const id = generateChunkId(parsed.path, chunk);
7236
8296
  const contentHash = generateChunkHash(chunk);
8297
+ const existingContentHash = existingChunks.get(id);
8298
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
8299
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
8300
+ const blameMetadata = metadataFromBlame(blame);
7237
8301
  currentChunkIds.add(id);
7238
8302
  chunkDataBatch.push({
7239
8303
  chunkId: id,
@@ -7243,15 +8307,20 @@ var Indexer = class {
7243
8307
  endLine: chunk.endLine,
7244
8308
  nodeType: chunk.chunkType,
7245
8309
  name: chunk.name,
7246
- language: chunk.language
8310
+ language: chunk.language,
8311
+ blameSha: blameMetadata.blameSha,
8312
+ blameAuthor: blameMetadata.blameAuthor,
8313
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8314
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8315
+ blameSummary: blameMetadata.blameSummary
7247
8316
  });
7248
- if (existingChunks.get(id) === contentHash) {
8317
+ if (existingContentHash === contentHash) {
7249
8318
  fileChunkCount++;
7250
8319
  continue;
7251
8320
  }
7252
- const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text2) => ({
7253
- text: text2,
7254
- tokenCount: estimateTokens(text2)
8321
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
8322
+ text: text3,
8323
+ tokenCount: estimateTokens(text3)
7255
8324
  }));
7256
8325
  const metadata = {
7257
8326
  filePath: parsed.path,
@@ -7260,7 +8329,8 @@ var Indexer = class {
7260
8329
  chunkType: chunk.chunkType,
7261
8330
  name: chunk.name,
7262
8331
  language: chunk.language,
7263
- hash: contentHash
8332
+ hash: contentHash,
8333
+ ...blameMetadata
7264
8334
  };
7265
8335
  pendingChunks.push({
7266
8336
  id,
@@ -7403,6 +8473,11 @@ var Indexer = class {
7403
8473
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7404
8474
  database.clearBranchSymbols(branchCatalogKey);
7405
8475
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8476
+ const vectorPath = path13.join(this.indexPath, "vectors");
8477
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
8478
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
8479
+ store.save();
8480
+ }
7406
8481
  if (scopedRoots) {
7407
8482
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7408
8483
  this.clearScopedFailedBatches(scopedRoots);
@@ -7421,7 +8496,6 @@ var Indexer = class {
7421
8496
  chunksProcessed: 0,
7422
8497
  totalChunks: 0
7423
8498
  });
7424
- this.releaseIndexingLock();
7425
8499
  return stats;
7426
8500
  }
7427
8501
  if (pendingChunks.length === 0) {
@@ -7430,7 +8504,7 @@ var Indexer = class {
7430
8504
  database.clearBranchSymbols(branchCatalogKey);
7431
8505
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7432
8506
  store.save();
7433
- invertedIndex.save();
8507
+ this.saveInvertedIndex(invertedIndex);
7434
8508
  if (scopedRoots) {
7435
8509
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7436
8510
  this.clearScopedFailedBatches(scopedRoots);
@@ -7449,7 +8523,6 @@ var Indexer = class {
7449
8523
  chunksProcessed: 0,
7450
8524
  totalChunks: 0
7451
8525
  });
7452
- this.releaseIndexingLock();
7453
8526
  return stats;
7454
8527
  }
7455
8528
  onProgress?.({
@@ -7680,7 +8753,7 @@ var Indexer = class {
7680
8753
  database.clearBranchSymbols(branchCatalogKey);
7681
8754
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7682
8755
  store.save();
7683
- invertedIndex.save();
8756
+ this.saveInvertedIndex(invertedIndex);
7684
8757
  if (scopedRoots) {
7685
8758
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7686
8759
  } else {
@@ -7732,7 +8805,6 @@ var Indexer = class {
7732
8805
  chunksProcessed: stats.indexedChunks,
7733
8806
  totalChunks: pendingChunks.length
7734
8807
  });
7735
- this.releaseIndexingLock();
7736
8808
  return stats;
7737
8809
  }
7738
8810
  async getQueryEmbedding(query, provider) {
@@ -7782,9 +8854,9 @@ var Indexer = class {
7782
8854
  }
7783
8855
  return bestMatch;
7784
8856
  }
7785
- tokenize(text2) {
8857
+ tokenize(text3) {
7786
8858
  return new Set(
7787
- text2.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
8859
+ text3.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
7788
8860
  );
7789
8861
  }
7790
8862
  jaccardSimilarity(a, b) {
@@ -7798,8 +8870,8 @@ var Indexer = class {
7798
8870
  return intersection / union;
7799
8871
  }
7800
8872
  async search(query, limit, options) {
7801
- const { store, provider, database } = await this.ensureInitialized();
7802
- const compatibility = this.checkCompatibility();
8873
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8874
+ this.requireReadableComponents(readIssues, "vectors", "database");
7803
8875
  if (!compatibility.compatible) {
7804
8876
  throw new Error(
7805
8877
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
@@ -7813,6 +8885,7 @@ var Indexer = class {
7813
8885
  const maxResults = limit ?? this.config.search.maxResults;
7814
8886
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
7815
8887
  const fusionStrategy = this.config.search.fusionStrategy;
8888
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
7816
8889
  const rrfK = this.config.search.rrfK;
7817
8890
  const rerankTopN = this.config.search.rerankTopN;
7818
8891
  const filterByBranch = options?.filterByBranch ?? true;
@@ -7821,7 +8894,7 @@ var Indexer = class {
7821
8894
  this.logger.search("debug", "Starting search", {
7822
8895
  query,
7823
8896
  maxResults,
7824
- hybridWeight,
8897
+ hybridWeight: effectiveHybridWeight,
7825
8898
  fusionStrategy,
7826
8899
  rrfK,
7827
8900
  rerankTopN,
@@ -7835,7 +8908,7 @@ var Indexer = class {
7835
8908
  const semanticResults = store.search(embedding, maxResults * 4);
7836
8909
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
7837
8910
  const keywordStartTime = import_perf_hooks.performance.now();
7838
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8911
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
7839
8912
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
7840
8913
  let branchChunkIds = null;
7841
8914
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -7872,7 +8945,7 @@ var Indexer = class {
7872
8945
  rrfK,
7873
8946
  rerankTopN,
7874
8947
  limit: maxResults,
7875
- hybridWeight,
8948
+ hybridWeight: effectiveHybridWeight,
7876
8949
  prioritizeSourcePaths: sourceIntent
7877
8950
  });
7878
8951
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -7946,7 +9019,7 @@ var Indexer = class {
7946
9019
  let contextEndLine = r.metadata.endLine;
7947
9020
  if (!metadataOnly && this.config.search.includeContext) {
7948
9021
  try {
7949
- const fileContent = await import_fs7.promises.readFile(
9022
+ const fileContent = await import_fs8.promises.readFile(
7950
9023
  r.metadata.filePath,
7951
9024
  "utf-8"
7952
9025
  );
@@ -7966,13 +9039,13 @@ var Indexer = class {
7966
9039
  content,
7967
9040
  score: r.score,
7968
9041
  chunkType: r.metadata.chunkType,
7969
- name: r.metadata.name
9042
+ name: r.metadata.name,
9043
+ blame: blameFromMetadata(r.metadata)
7970
9044
  };
7971
9045
  })
7972
9046
  );
7973
9047
  }
7974
- async keywordSearch(query, limit) {
7975
- const { store, invertedIndex } = await this.ensureInitialized();
9048
+ async keywordSearch(query, limit, store, invertedIndex) {
7976
9049
  const scores = invertedIndex.search(query);
7977
9050
  if (scores.size === 0) {
7978
9051
  return [];
@@ -7990,24 +9063,54 @@ var Indexer = class {
7990
9063
  return results.slice(0, limit);
7991
9064
  }
7992
9065
  async getStatus() {
7993
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9066
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
7994
9067
  const failedBatchesCount = this.getFailedBatchesCount();
9068
+ const vectorCount = store.count();
9069
+ const statusReadIssues = [...readIssues];
9070
+ let startupWarning = "";
9071
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9072
+ try {
9073
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9074
+ } catch (error) {
9075
+ const message = this.getDatabaseReadIssueMessage();
9076
+ statusReadIssues.push(this.createReadIssue("database", message));
9077
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9078
+ this.recordReadIssue("database", message, error);
9079
+ }
9080
+ }
9081
+ }
9082
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9083
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9084
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
7995
9085
  return {
7996
- indexed: store.count() > 0,
7997
- vectorCount: store.count(),
9086
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9087
+ vectorCount,
7998
9088
  provider: configuredProviderInfo.provider,
7999
9089
  model: configuredProviderInfo.modelInfo.model,
8000
9090
  indexPath: this.indexPath,
8001
9091
  currentBranch: this.currentBranch,
8002
9092
  baseBranch: this.baseBranch,
8003
- compatibility: this.indexCompatibility,
9093
+ compatibility,
8004
9094
  failedBatchesCount,
8005
9095
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8006
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9096
+ warning: warning || void 0
8007
9097
  };
8008
9098
  }
9099
+ async forceIndex(onProgress) {
9100
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9101
+ await this.ensureInitializedUnlocked(recoveredOwners);
9102
+ await this.clearIndexUnlocked();
9103
+ return this.indexUnlocked(onProgress, [], true);
9104
+ });
9105
+ }
8009
9106
  async clearIndex() {
8010
- const { store, invertedIndex, database } = await this.ensureInitialized();
9107
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9108
+ await this.ensureInitializedUnlocked(recoveredOwners);
9109
+ await this.clearIndexUnlocked();
9110
+ });
9111
+ }
9112
+ async clearIndexUnlocked() {
9113
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8011
9114
  if (this.config.scope === "global") {
8012
9115
  store.load();
8013
9116
  invertedIndex.load();
@@ -8034,7 +9137,7 @@ var Indexer = class {
8034
9137
  store.clear();
8035
9138
  store.save();
8036
9139
  invertedIndex.clear();
8037
- invertedIndex.save();
9140
+ this.saveInvertedIndex(invertedIndex);
8038
9141
  this.fileHashCache.clear();
8039
9142
  this.saveFileHashCache();
8040
9143
  database.clearAllIndexedData();
@@ -8058,14 +9161,7 @@ var Indexer = class {
8058
9161
  this.indexCompatibility = compatibility;
8059
9162
  return;
8060
9163
  }
8061
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8062
- if (this.host !== "opencode") {
8063
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8064
- }
8065
- const isLocalProjectIndex = localProjectIndexPaths.some(
8066
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8067
- );
8068
- if (!isLocalProjectIndex) {
9164
+ if (!this.isLocalProjectIndexPath()) {
8069
9165
  throw new Error(
8070
9166
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8071
9167
  );
@@ -8073,7 +9169,7 @@ var Indexer = class {
8073
9169
  store.clear();
8074
9170
  store.save();
8075
9171
  invertedIndex.clear();
8076
- invertedIndex.save();
9172
+ this.saveInvertedIndex(invertedIndex);
8077
9173
  this.fileHashCache.clear();
8078
9174
  this.saveFileHashCache();
8079
9175
  database.clearAllIndexedData();
@@ -8091,7 +9187,13 @@ var Indexer = class {
8091
9187
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8092
9188
  }
8093
9189
  async healthCheck() {
8094
- const { store, invertedIndex, database } = await this.ensureInitialized();
9190
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9191
+ await this.ensureInitializedUnlocked(recoveredOwners);
9192
+ return this.healthCheckUnlocked();
9193
+ });
9194
+ }
9195
+ async healthCheckUnlocked() {
9196
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8095
9197
  this.logger.gc("info", "Starting health check");
8096
9198
  const allMetadata = store.getAllMetadata();
8097
9199
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8104,7 +9206,7 @@ var Indexer = class {
8104
9206
  const removedChunkKeys = [];
8105
9207
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8106
9208
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8107
- if (!(0, import_fs7.existsSync)(filePath)) {
9209
+ if (!(0, import_fs8.existsSync)(filePath)) {
8108
9210
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8109
9211
  for (const key of chunkKeys) {
8110
9212
  removedChunkKeys.push(key);
@@ -8129,7 +9231,7 @@ var Indexer = class {
8129
9231
  const removedCount = removedChunkKeys.length;
8130
9232
  if (removedCount > 0) {
8131
9233
  store.save();
8132
- invertedIndex.save();
9234
+ this.saveInvertedIndex(invertedIndex);
8133
9235
  }
8134
9236
  let gcOrphanEmbeddings;
8135
9237
  let gcOrphanChunks;
@@ -8144,7 +9246,7 @@ var Indexer = class {
8144
9246
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8145
9247
  throw error;
8146
9248
  }
8147
- await this.ensureInitialized();
9249
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8148
9250
  return {
8149
9251
  removed: 0,
8150
9252
  filePaths: [],
@@ -8153,7 +9255,7 @@ var Indexer = class {
8153
9255
  gcOrphanSymbols: 0,
8154
9256
  gcOrphanCallEdges: 0,
8155
9257
  resetCorruptedIndex: true,
8156
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9258
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8157
9259
  };
8158
9260
  }
8159
9261
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8166,7 +9268,13 @@ var Indexer = class {
8166
9268
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8167
9269
  }
8168
9270
  async retryFailedBatches() {
8169
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9271
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9272
+ await this.ensureInitializedUnlocked(recoveredOwners);
9273
+ return this.retryFailedBatchesUnlocked();
9274
+ });
9275
+ }
9276
+ async retryFailedBatchesUnlocked() {
9277
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8170
9278
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8171
9279
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8172
9280
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8330,7 +9438,7 @@ var Indexer = class {
8330
9438
  }
8331
9439
  if (succeeded > 0) {
8332
9440
  store.save();
8333
- invertedIndex.save();
9441
+ this.saveInvertedIndex(invertedIndex);
8334
9442
  }
8335
9443
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8336
9444
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8358,15 +9466,16 @@ var Indexer = class {
8358
9466
  }
8359
9467
  }
8360
9468
  async getDatabaseStats() {
8361
- const { database } = await this.ensureInitialized();
9469
+ const { database, readIssues } = await this.ensureInitialized();
9470
+ this.requireReadableComponents(readIssues, "database");
8362
9471
  return database.getStats();
8363
9472
  }
8364
9473
  getLogger() {
8365
9474
  return this.logger;
8366
9475
  }
8367
9476
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8368
- const { store, provider, database } = await this.ensureInitialized();
8369
- const compatibility = this.checkCompatibility();
9477
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9478
+ this.requireReadableComponents(readIssues, "vectors", "database");
8370
9479
  if (!compatibility.compatible) {
8371
9480
  throw new Error(
8372
9481
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8456,7 +9565,7 @@ var Indexer = class {
8456
9565
  let content = "";
8457
9566
  if (this.config.search.includeContext) {
8458
9567
  try {
8459
- const fileContent = await import_fs7.promises.readFile(
9568
+ const fileContent = await import_fs8.promises.readFile(
8460
9569
  r.metadata.filePath,
8461
9570
  "utf-8"
8462
9571
  );
@@ -8473,13 +9582,15 @@ var Indexer = class {
8473
9582
  content,
8474
9583
  score: r.score,
8475
9584
  chunkType: r.metadata.chunkType,
8476
- name: r.metadata.name
9585
+ name: r.metadata.name,
9586
+ blame: blameFromMetadata(r.metadata)
8477
9587
  };
8478
9588
  })
8479
9589
  );
8480
9590
  }
8481
9591
  async getCallers(targetName, callTypeFilter) {
8482
- const { database } = await this.ensureInitialized();
9592
+ const { database, readIssues } = await this.ensureInitialized();
9593
+ this.requireReadableComponents(readIssues, "database");
8483
9594
  const seen = /* @__PURE__ */ new Set();
8484
9595
  const results = [];
8485
9596
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8493,7 +9604,8 @@ var Indexer = class {
8493
9604
  return results;
8494
9605
  }
8495
9606
  async getCallees(symbolId, callTypeFilter) {
8496
- const { database } = await this.ensureInitialized();
9607
+ const { database, readIssues } = await this.ensureInitialized();
9608
+ this.requireReadableComponents(readIssues, "database");
8497
9609
  const seen = /* @__PURE__ */ new Set();
8498
9610
  const results = [];
8499
9611
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8507,44 +9619,51 @@ var Indexer = class {
8507
9619
  return results;
8508
9620
  }
8509
9621
  async findCallPath(fromName, toName, maxDepth) {
8510
- const { database } = await this.ensureInitialized();
9622
+ const { database, readIssues } = await this.ensureInitialized();
9623
+ this.requireReadableComponents(readIssues, "database");
8511
9624
  let shortest = [];
8512
9625
  for (const branchKey of this.getBranchCatalogKeys()) {
8513
- const path15 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8514
- if (path15.length > 0 && (shortest.length === 0 || path15.length < shortest.length)) {
8515
- shortest = path15;
9626
+ const path17 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9627
+ if (path17.length > 0 && (shortest.length === 0 || path17.length < shortest.length)) {
9628
+ shortest = path17;
8516
9629
  }
8517
9630
  }
8518
9631
  return shortest;
8519
9632
  }
8520
9633
  async getSymbolsForBranch(branch) {
8521
- const { database } = await this.ensureInitialized();
9634
+ const { database, readIssues } = await this.ensureInitialized();
9635
+ this.requireReadableComponents(readIssues, "database");
8522
9636
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8523
9637
  return database.getSymbolsForBranch(resolvedBranch);
8524
9638
  }
8525
9639
  async getSymbolsForFiles(filePaths, branch) {
8526
- const { database } = await this.ensureInitialized();
9640
+ const { database, readIssues } = await this.ensureInitialized();
9641
+ this.requireReadableComponents(readIssues, "database");
8527
9642
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8528
9643
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8529
9644
  }
8530
9645
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8531
- const { database } = await this.ensureInitialized();
9646
+ const { database, readIssues } = await this.ensureInitialized();
9647
+ this.requireReadableComponents(readIssues, "database");
8532
9648
  const branch = this.getBranchCatalogKey();
8533
9649
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8534
9650
  }
8535
9651
  async detectCommunities(branch, symbolIds) {
8536
- const { database } = await this.ensureInitialized();
9652
+ const { database, readIssues } = await this.ensureInitialized();
9653
+ this.requireReadableComponents(readIssues, "database");
8537
9654
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8538
9655
  return database.detectCommunities(resolvedBranch, symbolIds);
8539
9656
  }
8540
9657
  async computeCentrality(branch) {
8541
- const { database } = await this.ensureInitialized();
9658
+ const { database, readIssues } = await this.ensureInitialized();
9659
+ this.requireReadableComponents(readIssues, "database");
8542
9660
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8543
9661
  return database.computeCentrality(resolvedBranch);
8544
9662
  }
8545
9663
  async getPrImpact(opts) {
8546
- const { database } = await this.ensureInitialized();
8547
- const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
9664
+ const { database, readIssues } = await this.ensureInitialized();
9665
+ this.requireReadableComponents(readIssues, "database");
9666
+ const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8548
9667
  const changedFilesResult = await getChangedFiles({
8549
9668
  pr: opts.pr,
8550
9669
  branch: opts.branch,
@@ -8566,7 +9685,7 @@ var Indexer = class {
8566
9685
  "Run index_codebase first to build the call graph and symbol index for this branch."
8567
9686
  );
8568
9687
  }
8569
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9688
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8570
9689
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8571
9690
  const directIds = directSymbols.map((s) => s.id);
8572
9691
  const direction = opts.direction ?? "both";
@@ -8628,7 +9747,7 @@ var Indexer = class {
8628
9747
  if (opts.checkConflicts) {
8629
9748
  conflictingPRs = [];
8630
9749
  try {
8631
- const { stdout } = await execFileAsync2(
9750
+ const { stdout } = await execFileAsync3(
8632
9751
  "gh",
8633
9752
  ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8634
9753
  { cwd: this.projectRoot, timeout: 3e4 }
@@ -8649,7 +9768,7 @@ var Indexer = class {
8649
9768
  projectRoot: this.projectRoot,
8650
9769
  baseBranch: this.baseBranch
8651
9770
  });
8652
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9771
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8653
9772
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8654
9773
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8655
9774
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8699,7 +9818,8 @@ var Indexer = class {
8699
9818
  };
8700
9819
  }
8701
9820
  async getVisualizationData(options) {
8702
- const { database, store } = await this.ensureInitialized();
9821
+ const { database, store, readIssues } = await this.ensureInitialized();
9822
+ this.requireReadableComponents(readIssues, "vectors", "database");
8703
9823
  const seenSymbols = /* @__PURE__ */ new Map();
8704
9824
  const seenEdges = /* @__PURE__ */ new Map();
8705
9825
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8712,12 +9832,12 @@ var Indexer = class {
8712
9832
  if (meta.filePath) filePaths.add(meta.filePath);
8713
9833
  }
8714
9834
  const directory = options?.directory?.replace(/\/$/, "");
8715
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9835
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8716
9836
  for (const filePath of filePaths) {
8717
9837
  if (directory) {
8718
- const absoluteFilePath = path11.resolve(filePath);
9838
+ const absoluteFilePath = path13.resolve(filePath);
8719
9839
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8720
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9840
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8721
9841
  if (!matchesRelative && !matchesProjectRelative) {
8722
9842
  continue;
8723
9843
  }
@@ -8739,53 +9859,64 @@ var Indexer = class {
8739
9859
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8740
9860
  }
8741
9861
  async close() {
8742
- await this.database?.close();
9862
+ this.database?.close();
9863
+ for (const database of this.retiredDatabases) {
9864
+ database.close();
9865
+ }
9866
+ this.retiredDatabases = [];
8743
9867
  this.database = null;
8744
9868
  this.store = null;
8745
9869
  this.invertedIndex = null;
8746
9870
  this.provider = null;
8747
9871
  this.reranker = null;
9872
+ this.configuredProviderInfo = null;
9873
+ this.indexCompatibility = null;
9874
+ this.initializationMode = "none";
9875
+ this.readIssues = [];
9876
+ this.readerArtifactFingerprint = null;
9877
+ this.writerArtifactFingerprint = null;
9878
+ this.readerArtifactRetryAfter.clear();
8748
9879
  }
8749
9880
  };
8750
9881
 
8751
9882
  // src/tools/knowledge-base-paths.ts
8752
- var path12 = __toESM(require("path"), 1);
9883
+ var path14 = __toESM(require("path"), 1);
8753
9884
  function resolveConfigPathValue(value, baseDir) {
8754
9885
  const trimmed = value.trim();
8755
9886
  if (!trimmed) {
8756
9887
  return trimmed;
8757
9888
  }
8758
- const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
8759
- return path12.normalize(absolutePath);
9889
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9890
+ return path14.normalize(absolutePath);
8760
9891
  }
8761
9892
  function serializeConfigPathValue(value, baseDir) {
8762
9893
  const trimmed = value.trim();
8763
9894
  if (!trimmed) {
8764
9895
  return trimmed;
8765
9896
  }
8766
- if (!path12.isAbsolute(trimmed)) {
8767
- return normalizePathSeparators(path12.normalize(trimmed));
9897
+ if (!path14.isAbsolute(trimmed)) {
9898
+ return normalizePathSeparators(path14.normalize(trimmed));
8768
9899
  }
8769
- const relativePath = path12.relative(baseDir, trimmed);
8770
- if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
8771
- return normalizePathSeparators(path12.normalize(relativePath || "."));
9900
+ const relativePath = path14.relative(baseDir, trimmed);
9901
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9902
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8772
9903
  }
8773
- return path12.normalize(trimmed);
9904
+ return path14.normalize(trimmed);
8774
9905
  }
8775
- function resolveKnowledgeBasePath(value, projectRoot2) {
8776
- return path12.isAbsolute(value) ? value : path12.resolve(projectRoot2, value);
9906
+ function resolveKnowledgeBasePath(value, projectRoot3) {
9907
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
8777
9908
  }
8778
- function normalizeKnowledgeBasePath2(value, projectRoot2) {
8779
- return path12.normalize(resolveKnowledgeBasePath(value, projectRoot2));
9909
+ function normalizeKnowledgeBasePath2(value, projectRoot3) {
9910
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
8780
9911
  }
8781
- function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot2) {
8782
- const normalizedInput = path12.normalize(inputPath);
8783
- return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput);
9912
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
9913
+ const normalizedInput = path14.normalize(inputPath);
9914
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
8784
9915
  }
8785
- function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot2) {
8786
- const normalizedInput = path12.normalize(inputPath);
9916
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
9917
+ const normalizedInput = path14.normalize(inputPath);
8787
9918
  return knowledgeBases.findIndex(
8788
- (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput
9919
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
8789
9920
  );
8790
9921
  }
8791
9922
 
@@ -8885,6 +10016,10 @@ function formatStatus(status) {
8885
10016
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
8886
10017
  }
8887
10018
  }
10019
+ if (status.warning) {
10020
+ lines.push("");
10021
+ lines.push(`INDEX WARNING: ${status.warning}`);
10022
+ }
8888
10023
  if (status.compatibility && !status.compatibility.compatible) {
8889
10024
  lines.push("");
8890
10025
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8958,16 +10093,57 @@ function formatHealthCheck(result) {
8958
10093
  }
8959
10094
  return lines.join("\n");
8960
10095
  }
10096
+ function formatCallGraphCallers(name, callers, relationshipType) {
10097
+ if (callers.length === 0) {
10098
+ return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
10099
+ }
10100
+ const formatted = callers.map((edge, index) => {
10101
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10102
+ 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]"}`;
10103
+ });
10104
+ return `"${name}" is called by ${callers.length} function(s):
10105
+
10106
+ ${formatted.join("\n")}`;
10107
+ }
10108
+ function formatCallGraphCallees(symbolId, callees, relationshipType) {
10109
+ if (callees.length === 0) {
10110
+ return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
10111
+ }
10112
+ return callees.map((edge, index) => {
10113
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10114
+ return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
10115
+ }).join("\n");
10116
+ }
10117
+ function formatCallGraphPath(from, to, path17) {
10118
+ if (path17.length === 0) {
10119
+ return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
10120
+ }
10121
+ const formatted = path17.map((hop, index) => {
10122
+ const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
10123
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10124
+ return `${prefix} ${hop.symbolName}${location}`;
10125
+ });
10126
+ return `Path (${path17.length} hops):
10127
+ ${formatted.join("\n")}`;
10128
+ }
8961
10129
  function formatResultHeader(result, index) {
8962
10130
  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}`;
8963
10131
  }
10132
+ function formatBlame(result) {
10133
+ if (!result.blame) {
10134
+ return "";
10135
+ }
10136
+ const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
10137
+ return `
10138
+ ${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
10139
+ }
8964
10140
  function formatDefinitionLookup(results, query) {
8965
10141
  if (results.length === 0) {
8966
10142
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
8967
10143
  }
8968
10144
  const formatted = results.map((r, idx) => {
8969
10145
  const header = formatResultHeader(r, idx);
8970
- return `${header} (score: ${r.score.toFixed(2)})
10146
+ return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
8971
10147
  \`\`\`
8972
10148
  ${truncateContent(r.content)}
8973
10149
  \`\`\``;
@@ -8978,7 +10154,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
8978
10154
  const formatted = results.map((r, idx) => {
8979
10155
  const header = formatResultHeader(r, idx);
8980
10156
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
8981
- return `${header} ${scoreLabel}
10157
+ return `${header} ${scoreLabel}${formatBlame(r)}
8982
10158
  \`\`\`
8983
10159
  ${truncateContent(r.content)}
8984
10160
  \`\`\``;
@@ -8987,13 +10163,13 @@ ${truncateContent(r.content)}
8987
10163
  }
8988
10164
 
8989
10165
  // src/tools/config-state.ts
8990
- var import_fs8 = require("fs");
8991
- var path13 = __toESM(require("path"), 1);
8992
- function normalizeKnowledgeBasePaths(config, projectRoot2) {
10166
+ var import_fs9 = require("fs");
10167
+ var path15 = __toESM(require("path"), 1);
10168
+ function normalizeKnowledgeBasePaths(config, projectRoot3) {
8993
10169
  const normalized = { ...config };
8994
10170
  if (Array.isArray(normalized.knowledgeBases)) {
8995
10171
  normalized.knowledgeBases = normalized.knowledgeBases.map(
8996
- (kb) => resolveConfigPathValue(kb, projectRoot2)
10172
+ (kb) => resolveConfigPathValue(kb, projectRoot3)
8997
10173
  );
8998
10174
  }
8999
10175
  return normalized;
@@ -9004,21 +10180,21 @@ function toConfigRecord(rawConfig) {
9004
10180
  }
9005
10181
  return { ...rawConfig };
9006
10182
  }
9007
- function getConfigPath(projectRoot2, host = "opencode") {
9008
- return resolveWritableProjectConfigPath(projectRoot2, host);
10183
+ function getConfigPath(projectRoot3, host = "opencode") {
10184
+ return resolveWritableProjectConfigPath(projectRoot3, host);
9009
10185
  }
9010
- function loadRuntimeConfig(projectRoot2, host = "opencode") {
9011
- return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot2, host)), projectRoot2);
10186
+ function loadRuntimeConfig(projectRoot3, host = "opencode") {
10187
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot3, host)), projectRoot3);
9012
10188
  }
9013
- function loadEditableConfig(projectRoot2, host = "opencode") {
9014
- return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot2, host)), projectRoot2);
10189
+ function loadEditableConfig(projectRoot3, host = "opencode") {
10190
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot3, host)), projectRoot3);
9015
10191
  }
9016
- function saveConfig(projectRoot2, config, host = "opencode") {
9017
- const configPath = getConfigPath(projectRoot2, host);
9018
- const configDir = path13.dirname(configPath);
9019
- const configBaseDir = path13.dirname(configDir);
9020
- if (!(0, import_fs8.existsSync)(configDir)) {
9021
- (0, import_fs8.mkdirSync)(configDir, { recursive: true });
10192
+ function saveConfig(projectRoot3, config, host = "opencode") {
10193
+ const configPath = getConfigPath(projectRoot3, host);
10194
+ const configDir = path15.dirname(configPath);
10195
+ const configBaseDir = path15.dirname(configDir);
10196
+ if (!(0, import_fs9.existsSync)(configDir)) {
10197
+ (0, import_fs9.mkdirSync)(configDir, { recursive: true });
9022
10198
  }
9023
10199
  const serializableConfig = { ...config };
9024
10200
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9026,16 +10202,34 @@ function saveConfig(projectRoot2, config, host = "opencode") {
9026
10202
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9027
10203
  );
9028
10204
  }
9029
- (0, import_fs8.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10205
+ (0, import_fs9.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9030
10206
  }
9031
10207
 
9032
10208
  // src/tools/operations.ts
9033
10209
  var indexerCache = /* @__PURE__ */ new Map();
9034
10210
  var configCache = /* @__PURE__ */ new Map();
9035
10211
  var defaultProjectRoots = /* @__PURE__ */ new Map();
9036
- function getProjectRoot(projectRoot2, host) {
9037
- if (projectRoot2) {
9038
- return projectRoot2;
10212
+ function getIndexBusyResult(error) {
10213
+ if (!isIndexLockContentionError(error)) return null;
10214
+ const owner = error.owner;
10215
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10216
+ if (error.reason === "legacy-lock") {
10217
+ return {
10218
+ kind: "busy",
10219
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10220
+ };
10221
+ }
10222
+ if (error.reason === "unknown-owner") {
10223
+ return {
10224
+ kind: "busy",
10225
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10226
+ };
10227
+ }
10228
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10229
+ }
10230
+ function getProjectRoot(projectRoot3, host) {
10231
+ if (projectRoot3) {
10232
+ return projectRoot3;
9039
10233
  }
9040
10234
  const root = defaultProjectRoots.get(host);
9041
10235
  if (!root) {
@@ -9043,53 +10237,56 @@ function getProjectRoot(projectRoot2, host) {
9043
10237
  }
9044
10238
  return root;
9045
10239
  }
9046
- function getIndexerCacheKey(projectRoot2, host) {
9047
- return `${host}::${projectRoot2}`;
10240
+ function getIndexerCacheKey(projectRoot3, host) {
10241
+ return `${host}::${projectRoot3}`;
9048
10242
  }
9049
- function getOrCreateIndexer(projectRoot2, host) {
9050
- const key = getIndexerCacheKey(projectRoot2, host);
10243
+ function getOrCreateIndexer(projectRoot3, host) {
10244
+ const key = getIndexerCacheKey(projectRoot3, host);
9051
10245
  const cached = indexerCache.get(key);
9052
10246
  if (cached) {
9053
10247
  return cached;
9054
10248
  }
9055
- const config = parseConfig(loadRuntimeConfig(projectRoot2, host));
9056
- const indexer = new Indexer(projectRoot2, config, host);
10249
+ const config = parseConfig(loadRuntimeConfig(projectRoot3, host));
10250
+ const indexer = new Indexer(projectRoot3, config, host);
9057
10251
  indexerCache.set(key, indexer);
9058
10252
  configCache.set(key, config);
9059
10253
  return indexer;
9060
10254
  }
9061
- function getIndexerForProject(projectRoot2, host = "opencode") {
9062
- const root = getProjectRoot(projectRoot2, host);
10255
+ function getIndexerForProject(projectRoot3, host = "opencode") {
10256
+ const root = getProjectRoot(projectRoot3, host);
9063
10257
  return getOrCreateIndexer(root, host);
9064
10258
  }
9065
- function refreshIndexerForDirectory(projectRoot2, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot2, host))) {
9066
- const key = getIndexerCacheKey(projectRoot2, host);
9067
- const indexer = new Indexer(projectRoot2, config, host);
10259
+ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot3, host))) {
10260
+ const key = getIndexerCacheKey(projectRoot3, host);
10261
+ const indexer = new Indexer(projectRoot3, config, host);
9068
10262
  indexerCache.set(key, indexer);
9069
10263
  configCache.set(key, config);
9070
10264
  }
9071
- function shouldForceLocalizeProjectIndex(projectRoot2, host = "opencode") {
9072
- const root = getProjectRoot(projectRoot2, host);
9073
- const localIndexPath = path14.join(root, getHostProjectIndexRelativePath(host));
9074
- if ((0, import_fs9.existsSync)(localIndexPath)) {
10265
+ function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
10266
+ const root = getProjectRoot(projectRoot3, host);
10267
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10268
+ if ((0, import_fs10.existsSync)(localIndexPath)) {
9075
10269
  return false;
9076
10270
  }
9077
10271
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
9078
10272
  return inheritedIndexPath !== null;
9079
10273
  }
9080
- async function searchCodebase(projectRoot2, host, query, options = {}) {
9081
- const indexer = getIndexerForProject(projectRoot2, host);
10274
+ async function searchCodebase(projectRoot3, host, query, options = {}) {
10275
+ const indexer = getIndexerForProject(projectRoot3, host);
9082
10276
  return indexer.search(query, options.limit, {
9083
10277
  fileType: options.fileType,
9084
10278
  directory: options.directory,
9085
10279
  chunkType: options.chunkType,
9086
10280
  contextLines: options.contextLines,
9087
10281
  metadataOnly: options.metadataOnly,
9088
- definitionIntent: options.definitionIntent
10282
+ definitionIntent: options.definitionIntent,
10283
+ blameAuthor: options.blameAuthor,
10284
+ blameSha: options.blameSha,
10285
+ blameSince: options.blameSince
9089
10286
  });
9090
10287
  }
9091
- async function findSimilarCode(projectRoot2, host, code, options = {}) {
9092
- const indexer = getIndexerForProject(projectRoot2, host);
10288
+ async function findSimilarCode(projectRoot3, host, code, options = {}) {
10289
+ const indexer = getIndexerForProject(projectRoot3, host);
9093
10290
  return indexer.findSimilar(code, options.limit, {
9094
10291
  fileType: options.fileType,
9095
10292
  directory: options.directory,
@@ -9097,16 +10294,16 @@ async function findSimilarCode(projectRoot2, host, code, options = {}) {
9097
10294
  excludeFile: options.excludeFile
9098
10295
  });
9099
10296
  }
9100
- async function implementationLookup(projectRoot2, host, query, options = {}) {
9101
- const indexer = getIndexerForProject(projectRoot2, host);
10297
+ async function implementationLookup(projectRoot3, host, query, options = {}) {
10298
+ const indexer = getIndexerForProject(projectRoot3, host);
9102
10299
  return indexer.search(query, options.limit, {
9103
10300
  fileType: options.fileType,
9104
10301
  directory: options.directory,
9105
10302
  definitionIntent: true
9106
10303
  });
9107
10304
  }
9108
- async function getCallGraphData(projectRoot2, host, params) {
9109
- const indexer = getIndexerForProject(projectRoot2, host);
10305
+ async function getCallGraphData(projectRoot3, host, params) {
10306
+ const indexer = getIndexerForProject(projectRoot3, host);
9110
10307
  if (params.direction === "callees") {
9111
10308
  if (!params.symbolId) {
9112
10309
  return { direction: "callees", callees: [], callers: [] };
@@ -9117,53 +10314,73 @@ async function getCallGraphData(projectRoot2, host, params) {
9117
10314
  const callers = await indexer.getCallers(params.name, params.relationshipType);
9118
10315
  return { direction: "callers", callers, callees: [] };
9119
10316
  }
9120
- async function getCallGraphPath(projectRoot2, host, from, to, maxDepth) {
9121
- const indexer = getIndexerForProject(projectRoot2, host);
10317
+ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
10318
+ const indexer = getIndexerForProject(projectRoot3, host);
9122
10319
  return indexer.findCallPath(from, to, maxDepth);
9123
10320
  }
9124
- async function runIndexCodebase(projectRoot2, host, args, onProgress) {
9125
- const root = getProjectRoot(projectRoot2, host);
10321
+ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10322
+ const root = getProjectRoot(projectRoot3, host);
9126
10323
  const key = getIndexerCacheKey(root, host);
9127
- const cachedConfig = configCache.get(key);
9128
10324
  let indexer = getIndexerForProject(root, host);
9129
- if (args.estimateOnly) {
9130
- return { kind: "estimate", estimate: await indexer.estimateCost() };
9131
- }
9132
- if (args.force) {
9133
- if (shouldForceLocalizeProjectIndex(root, host)) {
9134
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
9135
- refreshIndexerForDirectory(root, host, cachedConfig);
9136
- indexer = getIndexerForProject(root, host);
9137
- }
9138
- await indexer.clearIndex();
9139
- refreshIndexerForDirectory(root, host, cachedConfig);
9140
- indexer = getIndexerForProject(root, host);
9141
- }
9142
- const stats = await indexer.index((progress) => {
9143
- if (onProgress) {
9144
- return onProgress(formatProgressTitle(progress), {
9145
- phase: progress.phase,
9146
- filesProcessed: progress.filesProcessed,
9147
- totalFiles: progress.totalFiles,
9148
- chunksProcessed: progress.chunksProcessed,
9149
- totalChunks: progress.totalChunks,
9150
- percentage: calculatePercentage(progress)
10325
+ const runtimeConfig = configCache.get(key);
10326
+ try {
10327
+ if (args.estimateOnly) {
10328
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10329
+ }
10330
+ const runIndex = async (target) => {
10331
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10332
+ return operation((progress) => {
10333
+ if (onProgress) {
10334
+ return onProgress(formatProgressTitle(progress), {
10335
+ phase: progress.phase,
10336
+ filesProcessed: progress.filesProcessed,
10337
+ totalFiles: progress.totalFiles,
10338
+ chunksProcessed: progress.chunksProcessed,
10339
+ totalChunks: progress.totalChunks,
10340
+ percentage: calculatePercentage(progress)
10341
+ });
10342
+ }
10343
+ return Promise.resolve();
9151
10344
  });
10345
+ };
10346
+ let stats;
10347
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10348
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10349
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10350
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10351
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10352
+ indexer = getIndexerForProject(root, host);
10353
+ return runIndex(indexer);
10354
+ }, { completeRecoveries: false });
10355
+ } else {
10356
+ stats = await runIndex(indexer);
9152
10357
  }
9153
- return Promise.resolve();
9154
- });
9155
- return { kind: "stats", stats };
10358
+ return { kind: "stats", stats };
10359
+ } catch (error) {
10360
+ const busyResult = getIndexBusyResult(error);
10361
+ if (!busyResult) throw error;
10362
+ return busyResult;
10363
+ }
9156
10364
  }
9157
- async function getIndexStatus(projectRoot2, host) {
9158
- const indexer = getIndexerForProject(projectRoot2, host);
10365
+ async function getIndexStatus(projectRoot3, host) {
10366
+ const indexer = getIndexerForProject(projectRoot3, host);
9159
10367
  return indexer.getStatus();
9160
10368
  }
9161
- async function getIndexHealthCheck(projectRoot2, host) {
9162
- const indexer = getIndexerForProject(projectRoot2, host);
10369
+ async function getIndexHealthCheck(projectRoot3, host) {
10370
+ const indexer = getIndexerForProject(projectRoot3, host);
9163
10371
  return indexer.healthCheck();
9164
10372
  }
9165
- async function getPrImpact(projectRoot2, host, params) {
9166
- const indexer = getIndexerForProject(projectRoot2, host);
10373
+ async function runIndexHealthCheck(projectRoot3, host) {
10374
+ try {
10375
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot3, host) };
10376
+ } catch (error) {
10377
+ const busyResult = getIndexBusyResult(error);
10378
+ if (!busyResult) throw error;
10379
+ return busyResult;
10380
+ }
10381
+ }
10382
+ async function getPrImpact(projectRoot3, host, params) {
10383
+ const indexer = getIndexerForProject(projectRoot3, host);
9167
10384
  return indexer.getPrImpact({
9168
10385
  pr: params.pr,
9169
10386
  branch: params.branch,
@@ -9173,8 +10390,8 @@ async function getPrImpact(projectRoot2, host, params) {
9173
10390
  direction: params.direction
9174
10391
  });
9175
10392
  }
9176
- async function getIndexMetrics(projectRoot2, host) {
9177
- const indexer = getIndexerForProject(projectRoot2, host);
10393
+ async function getIndexMetrics(projectRoot3, host) {
10394
+ const indexer = getIndexerForProject(projectRoot3, host);
9178
10395
  const logger = indexer.getLogger();
9179
10396
  if (!logger.isEnabled()) {
9180
10397
  return {
@@ -9196,8 +10413,8 @@ async function getIndexMetrics(projectRoot2, host) {
9196
10413
  text: logger.formatMetrics()
9197
10414
  };
9198
10415
  }
9199
- async function getIndexLogs(projectRoot2, host, args) {
9200
- const indexer = getIndexerForProject(projectRoot2, host);
10416
+ async function getIndexLogs(projectRoot3, host, args) {
10417
+ const indexer = getIndexerForProject(projectRoot3, host);
9201
10418
  const logger = indexer.getLogger();
9202
10419
  if (!logger.isEnabled()) {
9203
10420
  return {
@@ -9219,24 +10436,24 @@ async function getIndexLogs(projectRoot2, host, args) {
9219
10436
  text: "No logs recorded yet. Logs are captured during indexing and search operations."
9220
10437
  };
9221
10438
  }
9222
- const text2 = logs.map((entry) => {
10439
+ const text3 = logs.map((entry) => {
9223
10440
  const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
9224
10441
  return `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.category}] ${entry.message}${dataStr}`;
9225
10442
  }).join("\n");
9226
- return { kind: "entries", text: text2 };
10443
+ return { kind: "entries", text: text3 };
9227
10444
  }
9228
- function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9229
- const root = getProjectRoot(projectRoot2, host);
10445
+ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
10446
+ const root = getProjectRoot(projectRoot3, host);
9230
10447
  const inputPath = knowledgeBasePath.trim();
9231
- const normalizedPath = path14.resolve(
9232
- path14.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10448
+ const normalizedPath = path16.resolve(
10449
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9233
10450
  );
9234
- if (!(0, import_fs9.existsSync)(normalizedPath)) {
10451
+ if (!(0, import_fs10.existsSync)(normalizedPath)) {
9235
10452
  return `Error: Directory does not exist: ${normalizedPath}`;
9236
10453
  }
9237
10454
  let realPath;
9238
10455
  try {
9239
- realPath = (0, import_fs9.realpathSync)(normalizedPath);
10456
+ realPath = (0, import_fs10.realpathSync)(normalizedPath);
9240
10457
  } catch {
9241
10458
  return `Error: Cannot resolve path: ${normalizedPath}`;
9242
10459
  }
@@ -9265,13 +10482,13 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9265
10482
  }
9266
10483
  }
9267
10484
  for (const dotDir of sensitiveDotDirs) {
9268
- const sensitiveDir = path14.join(homeDir, dotDir);
10485
+ const sensitiveDir = path16.join(homeDir, dotDir);
9269
10486
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9270
10487
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9271
10488
  }
9272
10489
  }
9273
10490
  try {
9274
- const stat = (0, import_fs9.statSync)(normalizedPath);
10491
+ const stat = (0, import_fs10.statSync)(normalizedPath);
9275
10492
  if (!stat.isDirectory()) {
9276
10493
  return `Error: Path is not a directory: ${normalizedPath}`;
9277
10494
  }
@@ -9298,8 +10515,8 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9298
10515
  Run /index to rebuild the index with the new knowledge base.`;
9299
10516
  return result;
9300
10517
  }
9301
- function listKnowledgeBases(projectRoot2, host) {
9302
- const root = getProjectRoot(projectRoot2, host);
10518
+ function listKnowledgeBases(projectRoot3, host) {
10519
+ const root = getProjectRoot(projectRoot3, host);
9303
10520
  const config = loadRuntimeConfig(root, host);
9304
10521
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9305
10522
  if (knowledgeBases.length === 0) {
@@ -9311,7 +10528,7 @@ function listKnowledgeBases(projectRoot2, host) {
9311
10528
  for (let i = 0; i < knowledgeBases.length; i++) {
9312
10529
  const kb = knowledgeBases[i];
9313
10530
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9314
- const exists = (0, import_fs9.existsSync)(resolvedPath);
10531
+ const exists = (0, import_fs10.existsSync)(resolvedPath);
9315
10532
  result += `[${i + 1}] ${kb}
9316
10533
  `;
9317
10534
  result += ` Resolved: ${resolvedPath}
@@ -9320,7 +10537,7 @@ function listKnowledgeBases(projectRoot2, host) {
9320
10537
  `;
9321
10538
  if (exists) {
9322
10539
  try {
9323
- const stat = (0, import_fs9.statSync)(resolvedPath);
10540
+ const stat = (0, import_fs10.statSync)(resolvedPath);
9324
10541
  result += ` Type: ${stat.isDirectory() ? "Directory" : "File"}
9325
10542
  `;
9326
10543
  } catch {
@@ -9328,7 +10545,7 @@ function listKnowledgeBases(projectRoot2, host) {
9328
10545
  }
9329
10546
  result += "\n";
9330
10547
  }
9331
- const hasHostConfig = (0, import_fs9.existsSync)(path14.join(root, getHostProjectConfigRelativePath(host)));
10548
+ const hasHostConfig = (0, import_fs10.existsSync)(path16.join(root, getHostProjectConfigRelativePath(host)));
9332
10549
  if (hasHostConfig) {
9333
10550
  result += `
9334
10551
  Config sources: 1 file(s).`;
@@ -9337,8 +10554,8 @@ Config sources: 1 file(s).`;
9337
10554
  Config file: ${getConfigPath(root, host)}`;
9338
10555
  return result;
9339
10556
  }
9340
- function removeKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9341
- const root = getProjectRoot(projectRoot2, host);
10557
+ function removeKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
10558
+ const root = getProjectRoot(projectRoot3, host);
9342
10559
  const config = loadEditableConfig(root, host);
9343
10560
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9344
10561
  const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
@@ -9361,17 +10578,9 @@ Run /index to rebuild the index without the removed knowledge base.`;
9361
10578
  return result;
9362
10579
  }
9363
10580
 
9364
- // src/pi-extension.ts
10581
+ // src/pi-call-graph.ts
10582
+ var import_typebox = require("typebox");
9365
10583
  var HOST = "pi";
9366
- var ChunkType = import_typebox.Type.Union([
9367
- import_typebox.Type.Literal("function"),
9368
- import_typebox.Type.Literal("class"),
9369
- import_typebox.Type.Literal("method"),
9370
- import_typebox.Type.Literal("interface"),
9371
- import_typebox.Type.Literal("type"),
9372
- import_typebox.Type.Literal("module"),
9373
- import_typebox.Type.Literal("block")
9374
- ]);
9375
10584
  var RelationshipType = import_typebox.Type.Union([
9376
10585
  import_typebox.Type.Literal("Call"),
9377
10586
  import_typebox.Type.Literal("MethodCall"),
@@ -9380,214 +10589,250 @@ var RelationshipType = import_typebox.Type.Union([
9380
10589
  import_typebox.Type.Literal("Inherits"),
9381
10590
  import_typebox.Type.Literal("Implements")
9382
10591
  ]);
9383
- function text(text2, details) {
9384
- return { content: [{ type: "text", text: text2 }], details };
10592
+ function text(text3, details) {
10593
+ return { content: [{ type: "text", text: text3 }], details };
9385
10594
  }
9386
10595
  function projectRoot(ctx) {
9387
10596
  return ctx?.cwd ?? process.cwd();
9388
10597
  }
10598
+ function registerPiCallGraphTools(pi) {
10599
+ pi.registerTool({
10600
+ name: "call_graph",
10601
+ label: "Call Graph",
10602
+ description: "Find callers or callees of a function/method in the indexed call graph.",
10603
+ parameters: import_typebox.Type.Object({
10604
+ name: import_typebox.Type.String(),
10605
+ direction: import_typebox.Type.Optional(import_typebox.Type.Union([import_typebox.Type.Literal("callers"), import_typebox.Type.Literal("callees")])),
10606
+ symbolId: import_typebox.Type.Optional(import_typebox.Type.String()),
10607
+ relationshipType: import_typebox.Type.Optional(RelationshipType)
10608
+ }),
10609
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
10610
+ const result = await getCallGraphData(projectRoot(ctx), HOST, params);
10611
+ if (result.direction === "callees") {
10612
+ return text(
10613
+ params.symbolId ? formatCallGraphCallees(params.symbolId, result.callees, params.relationshipType) : "Error: 'symbolId' is required when direction is 'callees'.",
10614
+ result
10615
+ );
10616
+ }
10617
+ return text(formatCallGraphCallers(params.name, result.callers, params.relationshipType), result);
10618
+ }
10619
+ });
10620
+ pi.registerTool({
10621
+ name: "call_graph_path",
10622
+ label: "Call Graph Path",
10623
+ description: "Find a call path between two functions/methods.",
10624
+ parameters: import_typebox.Type.Object({
10625
+ from: import_typebox.Type.String(),
10626
+ to: import_typebox.Type.String(),
10627
+ maxDepth: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 10 }))
10628
+ }),
10629
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
10630
+ const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
10631
+ return text(formatCallGraphPath(params.from, params.to, result), result);
10632
+ }
10633
+ });
10634
+ }
10635
+
10636
+ // src/pi-extension.ts
10637
+ var HOST2 = "pi";
10638
+ var ChunkType = import_typebox2.Type.Union([
10639
+ import_typebox2.Type.Literal("function"),
10640
+ import_typebox2.Type.Literal("class"),
10641
+ import_typebox2.Type.Literal("method"),
10642
+ import_typebox2.Type.Literal("interface"),
10643
+ import_typebox2.Type.Literal("type"),
10644
+ import_typebox2.Type.Literal("module"),
10645
+ import_typebox2.Type.Literal("block")
10646
+ ]);
10647
+ function text2(text3, details) {
10648
+ return { content: [{ type: "text", text: text3 }], details };
10649
+ }
10650
+ function projectRoot2(ctx) {
10651
+ return ctx?.cwd ?? process.cwd();
10652
+ }
9389
10653
  function codebaseIndexPiExtension(pi) {
9390
10654
  pi.registerTool({
9391
10655
  name: "codebase_search",
9392
10656
  label: "Codebase Search",
9393
10657
  description: "Semantic search over the indexed codebase. Describe behavior, not syntax.",
9394
- parameters: import_typebox.Type.Object({
9395
- query: import_typebox.Type.String({ description: "Natural language description of what code you're looking for" }),
9396
- limit: import_typebox.Type.Optional(import_typebox.Type.Number({ description: "Maximum results (default: 10)" })),
9397
- fileType: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Filter by extension, e.g. ts, py, rs" })),
9398
- directory: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Filter by directory path" })),
9399
- chunkType: import_typebox.Type.Optional(ChunkType),
9400
- contextLines: import_typebox.Type.Optional(import_typebox.Type.Number({ description: "Extra lines around each match" }))
10658
+ parameters: import_typebox2.Type.Object({
10659
+ query: import_typebox2.Type.String({ description: "Natural language description of what code you're looking for" }),
10660
+ limit: import_typebox2.Type.Optional(import_typebox2.Type.Number({ description: "Maximum results (default: 10)" })),
10661
+ fileType: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by extension, e.g. ts, py, rs" })),
10662
+ directory: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by directory path" })),
10663
+ chunkType: import_typebox2.Type.Optional(ChunkType),
10664
+ contextLines: import_typebox2.Type.Optional(import_typebox2.Type.Number({ description: "Extra lines around each match" })),
10665
+ blameAuthor: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by git blame author name or email" })),
10666
+ blameSha: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by git blame commit SHA or prefix" })),
10667
+ blameSince: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter to chunks last changed on or after this date" }))
9401
10668
  }),
9402
10669
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9403
- const results = await searchCodebase(projectRoot(ctx), HOST, params.query, params);
9404
- return text(formatSearchResults(results), results);
10670
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, params);
10671
+ return text2(formatSearchResults(results), results);
9405
10672
  }
9406
10673
  });
9407
10674
  pi.registerTool({
9408
10675
  name: "codebase_peek",
9409
10676
  label: "Codebase Peek",
9410
10677
  description: "Semantic search returning only metadata (file, lines, symbol) to save tokens.",
9411
- parameters: import_typebox.Type.Object({
9412
- query: import_typebox.Type.String(),
9413
- limit: import_typebox.Type.Optional(import_typebox.Type.Number()),
9414
- fileType: import_typebox.Type.Optional(import_typebox.Type.String()),
9415
- directory: import_typebox.Type.Optional(import_typebox.Type.String()),
9416
- chunkType: import_typebox.Type.Optional(ChunkType)
10678
+ parameters: import_typebox2.Type.Object({
10679
+ query: import_typebox2.Type.String(),
10680
+ limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
10681
+ fileType: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10682
+ directory: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10683
+ chunkType: import_typebox2.Type.Optional(ChunkType),
10684
+ blameAuthor: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10685
+ blameSha: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10686
+ blameSince: import_typebox2.Type.Optional(import_typebox2.Type.String())
9417
10687
  }),
9418
10688
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9419
- const results = await searchCodebase(projectRoot(ctx), HOST, params.query, { ...params, metadataOnly: true });
9420
- return text(formatSearchResults(results), results);
10689
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, { ...params, metadataOnly: true });
10690
+ return text2(formatSearchResults(results), results);
9421
10691
  }
9422
10692
  });
9423
10693
  pi.registerTool({
9424
10694
  name: "find_similar",
9425
10695
  label: "Find Similar Code",
9426
10696
  description: "Find code similar to a snippet for duplicate detection and pattern discovery.",
9427
- parameters: import_typebox.Type.Object({
9428
- code: import_typebox.Type.String({ description: "Code snippet to compare" }),
9429
- limit: import_typebox.Type.Optional(import_typebox.Type.Number()),
9430
- fileType: import_typebox.Type.Optional(import_typebox.Type.String()),
9431
- directory: import_typebox.Type.Optional(import_typebox.Type.String()),
9432
- chunkType: import_typebox.Type.Optional(ChunkType),
9433
- excludeFile: import_typebox.Type.Optional(import_typebox.Type.String())
10697
+ parameters: import_typebox2.Type.Object({
10698
+ code: import_typebox2.Type.String({ description: "Code snippet to compare" }),
10699
+ limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
10700
+ fileType: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10701
+ directory: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10702
+ chunkType: import_typebox2.Type.Optional(ChunkType),
10703
+ excludeFile: import_typebox2.Type.Optional(import_typebox2.Type.String())
9434
10704
  }),
9435
10705
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9436
- const results = await findSimilarCode(projectRoot(ctx), HOST, params.code, params);
9437
- return text(formatSearchResults(results, "similarity"), results);
10706
+ const results = await findSimilarCode(projectRoot2(ctx), HOST2, params.code, params);
10707
+ return text2(formatSearchResults(results, "similarity"), results);
9438
10708
  }
9439
10709
  });
9440
10710
  pi.registerTool({
9441
10711
  name: "implementation_lookup",
9442
10712
  label: "Implementation Lookup",
9443
10713
  description: "Find likely symbol definitions or implementations by name or natural language.",
9444
- parameters: import_typebox.Type.Object({
9445
- query: import_typebox.Type.String(),
9446
- limit: import_typebox.Type.Optional(import_typebox.Type.Number()),
9447
- fileType: import_typebox.Type.Optional(import_typebox.Type.String()),
9448
- directory: import_typebox.Type.Optional(import_typebox.Type.String())
10714
+ parameters: import_typebox2.Type.Object({
10715
+ query: import_typebox2.Type.String(),
10716
+ limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
10717
+ fileType: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10718
+ directory: import_typebox2.Type.Optional(import_typebox2.Type.String())
9449
10719
  }),
9450
10720
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9451
- const results = await implementationLookup(projectRoot(ctx), HOST, params.query, params);
9452
- return text(formatDefinitionLookup(results, params.query), results);
10721
+ const results = await implementationLookup(projectRoot2(ctx), HOST2, params.query, params);
10722
+ return text2(formatDefinitionLookup(results, params.query), results);
9453
10723
  }
9454
10724
  });
9455
10725
  pi.registerTool({
9456
10726
  name: "index_codebase",
9457
10727
  label: "Index Codebase",
9458
10728
  description: "Build or refresh the semantic codebase index.",
9459
- parameters: import_typebox.Type.Object({
9460
- force: import_typebox.Type.Optional(import_typebox.Type.Boolean({ default: false })),
9461
- estimateOnly: import_typebox.Type.Optional(import_typebox.Type.Boolean({ default: false })),
9462
- verbose: import_typebox.Type.Optional(import_typebox.Type.Boolean({ default: false }))
10729
+ parameters: import_typebox2.Type.Object({
10730
+ force: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false })),
10731
+ estimateOnly: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false })),
10732
+ verbose: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false }))
9463
10733
  }),
9464
10734
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9465
- const result = await runIndexCodebase(projectRoot(ctx), HOST, params);
9466
- return result.kind === "estimate" ? text(formatCostEstimate(result.estimate), result.estimate) : text(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
10735
+ const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
10736
+ if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
10737
+ if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
10738
+ return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
9467
10739
  }
9468
10740
  });
9469
10741
  pi.registerTool({
9470
10742
  name: "index_status",
9471
10743
  label: "Index Status",
9472
10744
  description: "Check index health and current status.",
9473
- parameters: import_typebox.Type.Object({}),
10745
+ parameters: import_typebox2.Type.Object({}),
9474
10746
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9475
- const status = await getIndexStatus(projectRoot(ctx), HOST);
9476
- return text(formatStatus(status), status);
10747
+ const status = await getIndexStatus(projectRoot2(ctx), HOST2);
10748
+ return text2(formatStatus(status), status);
9477
10749
  }
9478
10750
  });
9479
10751
  pi.registerTool({
9480
10752
  name: "index_health_check",
9481
10753
  label: "Index Health Check",
9482
10754
  description: "Garbage collect orphaned embeddings/chunks and report health.",
9483
- parameters: import_typebox.Type.Object({}),
10755
+ parameters: import_typebox2.Type.Object({}),
9484
10756
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9485
- const result = await getIndexHealthCheck(projectRoot(ctx), HOST);
9486
- return text(formatHealthCheck(result), result);
10757
+ const result = await runIndexHealthCheck(projectRoot2(ctx), HOST2);
10758
+ if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
10759
+ return text2(formatHealthCheck(result.health), result.health);
9487
10760
  }
9488
10761
  });
9489
10762
  pi.registerTool({
9490
10763
  name: "index_metrics",
9491
10764
  label: "Index Metrics",
9492
10765
  description: "Return collected performance metrics when debug metrics are enabled.",
9493
- parameters: import_typebox.Type.Object({}),
10766
+ parameters: import_typebox2.Type.Object({}),
9494
10767
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9495
- const result = await getIndexMetrics(projectRoot(ctx), HOST);
9496
- return text(result.text, result);
10768
+ const result = await getIndexMetrics(projectRoot2(ctx), HOST2);
10769
+ return text2(result.text, result);
9497
10770
  }
9498
10771
  });
9499
10772
  pi.registerTool({
9500
10773
  name: "index_logs",
9501
10774
  label: "Index Logs",
9502
10775
  description: "Return recent debug logs when debug logging is enabled.",
9503
- parameters: import_typebox.Type.Object({
9504
- limit: import_typebox.Type.Optional(import_typebox.Type.Number()),
9505
- category: import_typebox.Type.Optional(import_typebox.Type.Union([
9506
- import_typebox.Type.Literal("search"),
9507
- import_typebox.Type.Literal("embedding"),
9508
- import_typebox.Type.Literal("cache"),
9509
- import_typebox.Type.Literal("gc"),
9510
- import_typebox.Type.Literal("branch"),
9511
- import_typebox.Type.Literal("general")
10776
+ parameters: import_typebox2.Type.Object({
10777
+ limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
10778
+ category: import_typebox2.Type.Optional(import_typebox2.Type.Union([
10779
+ import_typebox2.Type.Literal("search"),
10780
+ import_typebox2.Type.Literal("embedding"),
10781
+ import_typebox2.Type.Literal("cache"),
10782
+ import_typebox2.Type.Literal("gc"),
10783
+ import_typebox2.Type.Literal("branch"),
10784
+ import_typebox2.Type.Literal("general")
9512
10785
  ])),
9513
- level: import_typebox.Type.Optional(import_typebox.Type.Union([import_typebox.Type.Literal("error"), import_typebox.Type.Literal("warn"), import_typebox.Type.Literal("info"), import_typebox.Type.Literal("debug")]))
9514
- }),
9515
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9516
- const result = await getIndexLogs(projectRoot(ctx), HOST, params);
9517
- return text(result.text, result);
9518
- }
9519
- });
9520
- pi.registerTool({
9521
- name: "call_graph",
9522
- label: "Call Graph",
9523
- description: "Find callers or callees of a function/method in the indexed call graph.",
9524
- parameters: import_typebox.Type.Object({
9525
- name: import_typebox.Type.String(),
9526
- direction: import_typebox.Type.Optional(import_typebox.Type.Union([import_typebox.Type.Literal("callers"), import_typebox.Type.Literal("callees")])),
9527
- symbolId: import_typebox.Type.Optional(import_typebox.Type.String()),
9528
- relationshipType: import_typebox.Type.Optional(RelationshipType)
9529
- }),
9530
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9531
- const result = await getCallGraphData(projectRoot(ctx), HOST, params);
9532
- return text(JSON.stringify(result, null, 2), result);
9533
- }
9534
- });
9535
- pi.registerTool({
9536
- name: "call_graph_path",
9537
- label: "Call Graph Path",
9538
- description: "Find a call path between two functions/methods.",
9539
- parameters: import_typebox.Type.Object({
9540
- from: import_typebox.Type.String(),
9541
- to: import_typebox.Type.String(),
9542
- maxDepth: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 10 }))
10786
+ level: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.Literal("error"), import_typebox2.Type.Literal("warn"), import_typebox2.Type.Literal("info"), import_typebox2.Type.Literal("debug")]))
9543
10787
  }),
9544
10788
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9545
- const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
9546
- return text(JSON.stringify(result, null, 2), result);
10789
+ const result = await getIndexLogs(projectRoot2(ctx), HOST2, params);
10790
+ return text2(result.text, result);
9547
10791
  }
9548
10792
  });
10793
+ registerPiCallGraphTools(pi);
9549
10794
  pi.registerTool({
9550
10795
  name: "pr_impact",
9551
10796
  label: "PR Impact",
9552
10797
  description: "Analyze PR or branch impact through changed symbols and call graph neighborhoods.",
9553
- parameters: import_typebox.Type.Object({
9554
- pr: import_typebox.Type.Optional(import_typebox.Type.Number()),
9555
- branch: import_typebox.Type.Optional(import_typebox.Type.String()),
9556
- maxDepth: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 5 })),
9557
- hubThreshold: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 10 })),
9558
- checkConflicts: import_typebox.Type.Optional(import_typebox.Type.Boolean({ default: false })),
9559
- direction: import_typebox.Type.Optional(import_typebox.Type.Union([import_typebox.Type.Literal("callers"), import_typebox.Type.Literal("callees"), import_typebox.Type.Literal("both")]))
10798
+ parameters: import_typebox2.Type.Object({
10799
+ pr: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
10800
+ branch: import_typebox2.Type.Optional(import_typebox2.Type.String()),
10801
+ maxDepth: import_typebox2.Type.Optional(import_typebox2.Type.Number({ default: 5 })),
10802
+ hubThreshold: import_typebox2.Type.Optional(import_typebox2.Type.Number({ default: 10 })),
10803
+ checkConflicts: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false })),
10804
+ direction: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.Literal("callers"), import_typebox2.Type.Literal("callees"), import_typebox2.Type.Literal("both")]))
9560
10805
  }),
9561
10806
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9562
- const result = await getPrImpact(projectRoot(ctx), HOST, params);
9563
- return text(formatPrImpact(result), result);
10807
+ const result = await getPrImpact(projectRoot2(ctx), HOST2, params);
10808
+ return text2(formatPrImpact(result), result);
9564
10809
  }
9565
10810
  });
9566
10811
  pi.registerTool({
9567
10812
  name: "knowledge_base_list",
9568
10813
  label: "List Knowledge Bases",
9569
10814
  description: "List configured knowledge-base paths included in the index.",
9570
- parameters: import_typebox.Type.Object({}),
10815
+ parameters: import_typebox2.Type.Object({}),
9571
10816
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9572
- return text(listKnowledgeBases(projectRoot(ctx), HOST));
10817
+ return text2(listKnowledgeBases(projectRoot2(ctx), HOST2));
9573
10818
  }
9574
10819
  });
9575
10820
  pi.registerTool({
9576
10821
  name: "knowledge_base_add",
9577
10822
  label: "Add Knowledge Base",
9578
10823
  description: "Add a knowledge-base path to the codebase index config.",
9579
- parameters: import_typebox.Type.Object({ path: import_typebox.Type.String({ description: "File or directory path to add" }) }),
10824
+ parameters: import_typebox2.Type.Object({ path: import_typebox2.Type.String({ description: "File or directory path to add" }) }),
9580
10825
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9581
- return text(addKnowledgeBase(projectRoot(ctx), HOST, params.path));
10826
+ return text2(addKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9582
10827
  }
9583
10828
  });
9584
10829
  pi.registerTool({
9585
10830
  name: "knowledge_base_remove",
9586
10831
  label: "Remove Knowledge Base",
9587
10832
  description: "Remove a knowledge-base path from the codebase index config.",
9588
- parameters: import_typebox.Type.Object({ path: import_typebox.Type.String({ description: "File or directory path to remove" }) }),
10833
+ parameters: import_typebox2.Type.Object({ path: import_typebox2.Type.String({ description: "File or directory path to remove" }) }),
9589
10834
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9590
- return text(removeKnowledgeBase(projectRoot(ctx), HOST, params.path));
10835
+ return text2(removeKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9591
10836
  }
9592
10837
  });
9593
10838
  }