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.
@@ -490,7 +490,7 @@ var require_ignore = __commonJS({
490
490
  // path matching.
491
491
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
492
492
  // @returns {TestResult} true if a file is ignored
493
- test(path15, checkUnignored, mode) {
493
+ test(path17, checkUnignored, mode) {
494
494
  let ignored = false;
495
495
  let unignored = false;
496
496
  let matchedRule;
@@ -499,7 +499,7 @@ var require_ignore = __commonJS({
499
499
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
500
500
  return;
501
501
  }
502
- const matched = rule[mode].test(path15);
502
+ const matched = rule[mode].test(path17);
503
503
  if (!matched) {
504
504
  return;
505
505
  }
@@ -520,17 +520,17 @@ var require_ignore = __commonJS({
520
520
  var throwError = (message, Ctor) => {
521
521
  throw new Ctor(message);
522
522
  };
523
- var checkPath = (path15, originalPath, doThrow) => {
524
- if (!isString(path15)) {
523
+ var checkPath = (path17, originalPath, doThrow) => {
524
+ if (!isString(path17)) {
525
525
  return doThrow(
526
526
  `path must be a string, but got \`${originalPath}\``,
527
527
  TypeError
528
528
  );
529
529
  }
530
- if (!path15) {
530
+ if (!path17) {
531
531
  return doThrow(`path must not be empty`, TypeError);
532
532
  }
533
- if (checkPath.isNotRelative(path15)) {
533
+ if (checkPath.isNotRelative(path17)) {
534
534
  const r = "`path.relative()`d";
535
535
  return doThrow(
536
536
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -539,7 +539,7 @@ var require_ignore = __commonJS({
539
539
  }
540
540
  return true;
541
541
  };
542
- var isNotRelative = (path15) => REGEX_TEST_INVALID_PATH.test(path15);
542
+ var isNotRelative = (path17) => REGEX_TEST_INVALID_PATH.test(path17);
543
543
  checkPath.isNotRelative = isNotRelative;
544
544
  checkPath.convert = (p) => p;
545
545
  var Ignore2 = class {
@@ -569,19 +569,19 @@ var require_ignore = __commonJS({
569
569
  }
570
570
  // @returns {TestResult}
571
571
  _test(originalPath, cache, checkUnignored, slices) {
572
- const path15 = originalPath && checkPath.convert(originalPath);
572
+ const path17 = originalPath && checkPath.convert(originalPath);
573
573
  checkPath(
574
- path15,
574
+ path17,
575
575
  originalPath,
576
576
  this._strictPathCheck ? throwError : RETURN_FALSE
577
577
  );
578
- return this._t(path15, cache, checkUnignored, slices);
578
+ return this._t(path17, cache, checkUnignored, slices);
579
579
  }
580
- checkIgnore(path15) {
581
- if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
582
- return this.test(path15);
580
+ checkIgnore(path17) {
581
+ if (!REGEX_TEST_TRAILING_SLASH.test(path17)) {
582
+ return this.test(path17);
583
583
  }
584
- const slices = path15.split(SLASH).filter(Boolean);
584
+ const slices = path17.split(SLASH).filter(Boolean);
585
585
  slices.pop();
586
586
  if (slices.length) {
587
587
  const parent = this._t(
@@ -594,18 +594,18 @@ var require_ignore = __commonJS({
594
594
  return parent;
595
595
  }
596
596
  }
597
- return this._rules.test(path15, false, MODE_CHECK_IGNORE);
597
+ return this._rules.test(path17, false, MODE_CHECK_IGNORE);
598
598
  }
599
- _t(path15, cache, checkUnignored, slices) {
600
- if (path15 in cache) {
601
- return cache[path15];
599
+ _t(path17, cache, checkUnignored, slices) {
600
+ if (path17 in cache) {
601
+ return cache[path17];
602
602
  }
603
603
  if (!slices) {
604
- slices = path15.split(SLASH).filter(Boolean);
604
+ slices = path17.split(SLASH).filter(Boolean);
605
605
  }
606
606
  slices.pop();
607
607
  if (!slices.length) {
608
- return cache[path15] = this._rules.test(path15, checkUnignored, MODE_IGNORE);
608
+ return cache[path17] = this._rules.test(path17, checkUnignored, MODE_IGNORE);
609
609
  }
610
610
  const parent = this._t(
611
611
  slices.join(SLASH) + SLASH,
@@ -613,29 +613,29 @@ var require_ignore = __commonJS({
613
613
  checkUnignored,
614
614
  slices
615
615
  );
616
- return cache[path15] = parent.ignored ? parent : this._rules.test(path15, checkUnignored, MODE_IGNORE);
616
+ return cache[path17] = parent.ignored ? parent : this._rules.test(path17, checkUnignored, MODE_IGNORE);
617
617
  }
618
- ignores(path15) {
619
- return this._test(path15, this._ignoreCache, false).ignored;
618
+ ignores(path17) {
619
+ return this._test(path17, this._ignoreCache, false).ignored;
620
620
  }
621
621
  createFilter() {
622
- return (path15) => !this.ignores(path15);
622
+ return (path17) => !this.ignores(path17);
623
623
  }
624
624
  filter(paths) {
625
625
  return makeArray(paths).filter(this.createFilter());
626
626
  }
627
627
  // @returns {TestResult}
628
- test(path15) {
629
- return this._test(path15, this._testCache, true);
628
+ test(path17) {
629
+ return this._test(path17, this._testCache, true);
630
630
  }
631
631
  };
632
632
  var factory = (options) => new Ignore2(options);
633
- var isPathValid = (path15) => checkPath(path15 && checkPath.convert(path15), path15, RETURN_FALSE);
633
+ var isPathValid = (path17) => checkPath(path17 && checkPath.convert(path17), path17, RETURN_FALSE);
634
634
  var setupWindows = () => {
635
635
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
636
636
  checkPath.convert = makePosix;
637
637
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
638
- checkPath.isNotRelative = (path15) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path15) || isNotRelative(path15);
638
+ checkPath.isNotRelative = (path17) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path17) || isNotRelative(path17);
639
639
  };
640
640
  if (
641
641
  // Detect `process` so that it can run in browsers.
@@ -651,14 +651,14 @@ var require_ignore = __commonJS({
651
651
  });
652
652
 
653
653
  // src/pi-extension.ts
654
- import { Type } from "typebox";
654
+ import { Type as Type2 } from "typebox";
655
655
 
656
656
  // src/config/constants.ts
657
657
  var DEFAULT_INCLUDE = [
658
- "**/*.{ts,tsx,js,jsx,mjs,cjs}",
658
+ "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
659
659
  "**/*.{py,pyi}",
660
- "**/*.{go,rs,java,kt,scala}",
661
- "**/*.{c,cpp,cc,h,hpp}",
660
+ "**/*.{go,rs,java,cs,kt,scala}",
661
+ "**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
662
662
  "**/*.{rb,php,inc,swift}",
663
663
  "**/*.{cls,trigger}",
664
664
  "**/*.{vue,svelte,astro}",
@@ -783,7 +783,8 @@ function getDefaultIndexingConfig() {
783
783
  requireProjectMarker: true,
784
784
  maxDepth: 5,
785
785
  maxFilesPerDirectory: 100,
786
- fallbackToTextOnMaxChunks: true
786
+ fallbackToTextOnMaxChunks: true,
787
+ gitBlame: { enabled: false }
787
788
  };
788
789
  }
789
790
  function getDefaultSearchConfig() {
@@ -907,7 +908,10 @@ function parseConfig(raw) {
907
908
  requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
908
909
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
909
910
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
910
- fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
911
+ fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
912
+ gitBlame: {
913
+ enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
914
+ }
911
915
  };
912
916
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
913
917
  const search = {
@@ -1323,8 +1327,8 @@ function formatPrImpact(result) {
1323
1327
  }
1324
1328
 
1325
1329
  // src/tools/operations.ts
1326
- import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
1327
- import * as path14 from "path";
1330
+ import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
1331
+ import * as path16 from "path";
1328
1332
 
1329
1333
  // src/config/merger.ts
1330
1334
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
@@ -1494,21 +1498,21 @@ function getProjectIndexRelativePath(host) {
1494
1498
  return CODEBASE_PROJECT_INDEX_RELATIVE_PATH;
1495
1499
  }
1496
1500
  }
1497
- function resolveWorktreeFallbackPath(projectRoot2, relativePath) {
1498
- const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot2);
1501
+ function resolveWorktreeFallbackPath(projectRoot3, relativePath) {
1502
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot3);
1499
1503
  if (!mainRepoRoot) {
1500
1504
  return null;
1501
1505
  }
1502
1506
  const fallbackPath = path4.join(mainRepoRoot, relativePath);
1503
1507
  return existsSync4(fallbackPath) ? fallbackPath : null;
1504
1508
  }
1505
- function resolveWorktreeFallbackProjectIndexPath(projectRoot2, host) {
1506
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot2, getProjectIndexRelativePath(host));
1509
+ function resolveWorktreeFallbackProjectIndexPath(projectRoot3, host) {
1510
+ const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1507
1511
  if (inheritedHostPath) {
1508
1512
  return inheritedHostPath;
1509
1513
  }
1510
1514
  if (host !== "opencode") {
1511
- return resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1515
+ return resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1512
1516
  }
1513
1517
  return null;
1514
1518
  }
@@ -1518,8 +1522,11 @@ function getHostProjectConfigRelativePath(host) {
1518
1522
  function getHostProjectIndexRelativePath(host) {
1519
1523
  return getProjectIndexRelativePath(host);
1520
1524
  }
1521
- function hasHostProjectConfig(projectRoot2, host) {
1522
- return existsSync4(path4.join(projectRoot2, getProjectConfigRelativePath(host)));
1525
+ function hasHostProjectConfig(projectRoot3, host) {
1526
+ return existsSync4(path4.join(projectRoot3, getProjectConfigRelativePath(host)));
1527
+ }
1528
+ function hasHostGlobalConfig(host) {
1529
+ return existsSync4(getGlobalConfigPath(host));
1523
1530
  }
1524
1531
  function getGlobalIndexPath(host = "opencode") {
1525
1532
  switch (host) {
@@ -1560,6 +1567,9 @@ function resolveGlobalIndexPath(host = "opencode") {
1560
1567
  return hostIndexPath;
1561
1568
  }
1562
1569
  if (host !== "opencode") {
1570
+ if (hasHostGlobalConfig(host)) {
1571
+ return hostIndexPath;
1572
+ }
1563
1573
  const legacyIndexPath = getGlobalIndexPath("opencode");
1564
1574
  if (existsSync4(legacyIndexPath)) {
1565
1575
  return legacyIndexPath;
@@ -1567,55 +1577,55 @@ function resolveGlobalIndexPath(host = "opencode") {
1567
1577
  }
1568
1578
  return hostIndexPath;
1569
1579
  }
1570
- function resolveProjectConfigPath(projectRoot2, host = "opencode") {
1571
- const hostConfigPath = path4.join(projectRoot2, getProjectConfigRelativePath(host));
1580
+ function resolveProjectConfigPath(projectRoot3, host = "opencode") {
1581
+ const hostConfigPath = path4.join(projectRoot3, getProjectConfigRelativePath(host));
1572
1582
  if (existsSync4(hostConfigPath)) {
1573
1583
  return hostConfigPath;
1574
1584
  }
1575
1585
  if (host !== "opencode") {
1576
- const legacyConfigPath = path4.join(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1586
+ const legacyConfigPath = path4.join(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1577
1587
  if (existsSync4(legacyConfigPath)) {
1578
1588
  return legacyConfigPath;
1579
1589
  }
1580
1590
  }
1581
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectConfigRelativePath(host));
1591
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectConfigRelativePath(host));
1582
1592
  if (hostFallback) {
1583
1593
  return hostFallback;
1584
1594
  }
1585
1595
  if (host !== "opencode") {
1586
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1596
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1587
1597
  if (legacyFallback) {
1588
1598
  return legacyFallback;
1589
1599
  }
1590
1600
  }
1591
1601
  return hostConfigPath;
1592
1602
  }
1593
- function resolveWritableProjectConfigPath(projectRoot2, host = "opencode") {
1594
- return path4.join(projectRoot2, getProjectConfigRelativePath(host));
1603
+ function resolveWritableProjectConfigPath(projectRoot3, host = "opencode") {
1604
+ return path4.join(projectRoot3, getProjectConfigRelativePath(host));
1595
1605
  }
1596
- function resolveProjectIndexPath(projectRoot2, scope, host = "opencode") {
1606
+ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1597
1607
  if (scope === "global") {
1598
1608
  return resolveGlobalIndexPath(host);
1599
1609
  }
1600
- const localIndexPath = path4.join(projectRoot2, getProjectIndexRelativePath(host));
1610
+ const localIndexPath = path4.join(projectRoot3, getProjectIndexRelativePath(host));
1601
1611
  if (existsSync4(localIndexPath)) {
1602
1612
  return localIndexPath;
1603
1613
  }
1604
1614
  if (host !== "opencode") {
1605
- const legacyIndexPath = path4.join(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1606
- if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(projectRoot2, host)) {
1615
+ const legacyIndexPath = path4.join(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1616
+ if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(projectRoot3, host)) {
1607
1617
  return legacyIndexPath;
1608
1618
  }
1609
1619
  }
1610
- if (hasHostProjectConfig(projectRoot2, host)) {
1620
+ if (hasHostProjectConfig(projectRoot3, host)) {
1611
1621
  return localIndexPath;
1612
1622
  }
1613
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectIndexRelativePath(host));
1623
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1614
1624
  if (hostFallback) {
1615
1625
  return hostFallback;
1616
1626
  }
1617
1627
  if (host !== "opencode") {
1618
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1628
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1619
1629
  if (legacyFallback) {
1620
1630
  return legacyFallback;
1621
1631
  }
@@ -1748,14 +1758,14 @@ function loadJsonFile(filePath) {
1748
1758
  throw new Error(`Failed to load config file ${filePath}: ${message}`);
1749
1759
  }
1750
1760
  }
1751
- function materializeLocalProjectConfig(projectRoot2, config, host = "opencode") {
1752
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot2, host);
1761
+ function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
1762
+ const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
1753
1763
  mkdirSync(path7.dirname(localConfigPath), { recursive: true });
1754
1764
  writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1755
1765
  return localConfigPath;
1756
1766
  }
1757
- function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1758
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1767
+ function loadProjectConfigLayer(projectRoot3, host = "opencode") {
1768
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1759
1769
  const projectConfig = loadJsonFile(projectConfigPath);
1760
1770
  if (!projectConfig) {
1761
1771
  return {};
@@ -1766,14 +1776,14 @@ function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1766
1776
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1767
1777
  normalizedConfig.knowledgeBases,
1768
1778
  projectConfigBaseDir,
1769
- projectRoot2
1779
+ projectRoot3
1770
1780
  );
1771
1781
  }
1772
1782
  return normalizedConfig;
1773
1783
  }
1774
- function loadMergedConfig(projectRoot2, host = "opencode") {
1784
+ function loadMergedConfig(projectRoot3, host = "opencode") {
1775
1785
  const globalConfigPath = resolveGlobalConfigPath(host);
1776
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1786
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1777
1787
  let globalConfig = null;
1778
1788
  let globalConfigError = null;
1779
1789
  try {
@@ -1782,7 +1792,7 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1782
1792
  globalConfigError = error instanceof Error ? error : new Error(String(error));
1783
1793
  }
1784
1794
  const projectConfig = loadJsonFile(projectConfigPath);
1785
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot2, host);
1795
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
1786
1796
  if (globalConfigError) {
1787
1797
  if (!projectConfig) {
1788
1798
  throw globalConfigError;
@@ -1825,11 +1835,11 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1825
1835
  }
1826
1836
 
1827
1837
  // src/indexer/index.ts
1828
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
1829
- import * as path11 from "path";
1838
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync3, promises as fsPromises2 } from "fs";
1839
+ import * as path13 from "path";
1830
1840
  import { performance as performance2 } from "perf_hooks";
1831
- import { execFile as execFile2 } from "child_process";
1832
- import { promisify as promisify2 } from "util";
1841
+ import { execFile as execFile3 } from "child_process";
1842
+ import { promisify as promisify3 } from "util";
1833
1843
 
1834
1844
  // node_modules/eventemitter3/index.mjs
1835
1845
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -2933,17 +2943,17 @@ function validateExternalUrl(urlString) {
2933
2943
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2934
2944
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2935
2945
  }
2936
- const hostname = parsed.hostname.toLowerCase();
2937
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2938
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
2946
+ const hostname2 = parsed.hostname.toLowerCase();
2947
+ if (BLOCKED_HOSTNAMES.has(hostname2)) {
2948
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
2939
2949
  }
2940
2950
  for (const pattern of BLOCKED_METADATA_IPS) {
2941
- if (pattern.test(hostname)) {
2942
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
2951
+ if (pattern.test(hostname2)) {
2952
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2943
2953
  }
2944
2954
  }
2945
- if (/^169\.254\./.test(hostname)) {
2946
- return { valid: false, reason: `Blocked: link-local address (${hostname})` };
2955
+ if (/^169\.254\./.test(hostname2)) {
2956
+ return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
2947
2957
  }
2948
2958
  return { valid: true };
2949
2959
  }
@@ -3137,10 +3147,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3137
3147
  }
3138
3148
  const batchResults = await Promise.all(
3139
3149
  batches.map(async (batch) => {
3140
- const requests = batch.map((text2) => ({
3150
+ const requests = batch.map((text3) => ({
3141
3151
  model: `models/${this.modelInfo.model}`,
3142
3152
  content: {
3143
- parts: [{ text: text2 }]
3153
+ parts: [{ text: text3 }]
3144
3154
  },
3145
3155
  taskType,
3146
3156
  outputDimensionality: this.modelInfo.dimensions
@@ -3163,7 +3173,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3163
3173
  const data = await response.json();
3164
3174
  return {
3165
3175
  embeddings: data.embeddings.map((e) => e.values),
3166
- tokensUsed: batch.reduce((sum, text2) => sum + Math.ceil(text2.length / 4), 0)
3176
+ tokensUsed: batch.reduce((sum, text3) => sum + Math.ceil(text3.length / 4), 0)
3167
3177
  };
3168
3178
  })
3169
3179
  );
@@ -3180,28 +3190,28 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3180
3190
  constructor(credentials, modelInfo) {
3181
3191
  super(credentials, modelInfo);
3182
3192
  }
3183
- estimateTokens(text2) {
3184
- return Math.ceil(text2.length / 4);
3193
+ estimateTokens(text3) {
3194
+ return Math.ceil(text3.length / 4);
3185
3195
  }
3186
- truncateToCharLimit(text2, maxChars) {
3187
- if (text2.length <= maxChars) {
3188
- return text2;
3196
+ truncateToCharLimit(text3, maxChars) {
3197
+ if (text3.length <= maxChars) {
3198
+ return text3;
3189
3199
  }
3190
- return `${text2.slice(0, Math.max(0, maxChars - 17))}
3200
+ return `${text3.slice(0, Math.max(0, maxChars - 17))}
3191
3201
  ... [truncated]`;
3192
3202
  }
3193
3203
  isContextLengthError(error) {
3194
3204
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
3195
3205
  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");
3196
3206
  }
3197
- buildTruncationCandidates(text2) {
3207
+ buildTruncationCandidates(text3) {
3198
3208
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
3199
3209
  const candidateLimits = /* @__PURE__ */ new Set();
3200
- const baselineLimit = text2.length > baseMaxChars ? baseMaxChars : Math.max(
3210
+ const baselineLimit = text3.length > baseMaxChars ? baseMaxChars : Math.max(
3201
3211
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3202
- Math.floor(text2.length * 0.9)
3212
+ Math.floor(text3.length * 0.9)
3203
3213
  );
3204
- if (baselineLimit < text2.length) {
3214
+ if (baselineLimit < text3.length) {
3205
3215
  candidateLimits.add(baselineLimit);
3206
3216
  }
3207
3217
  for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
@@ -3209,19 +3219,19 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3209
3219
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3210
3220
  Math.floor(baselineLimit * factor)
3211
3221
  );
3212
- if (scaledLimit < text2.length) {
3222
+ if (scaledLimit < text3.length) {
3213
3223
  candidateLimits.add(scaledLimit);
3214
3224
  }
3215
3225
  }
3216
- candidateLimits.add(Math.min(text2.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3226
+ candidateLimits.add(Math.min(text3.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3217
3227
  const candidates = [];
3218
3228
  const seen = /* @__PURE__ */ new Set();
3219
3229
  for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
3220
- if (limit <= 0 || limit >= text2.length) {
3230
+ if (limit <= 0 || limit >= text3.length) {
3221
3231
  continue;
3222
3232
  }
3223
- const truncated = this.truncateToCharLimit(text2, limit);
3224
- if (truncated === text2 || seen.has(truncated)) {
3233
+ const truncated = this.truncateToCharLimit(text3, limit);
3234
+ if (truncated === text3 || seen.has(truncated)) {
3225
3235
  continue;
3226
3236
  }
3227
3237
  seen.add(truncated);
@@ -3229,15 +3239,15 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3229
3239
  }
3230
3240
  return candidates;
3231
3241
  }
3232
- async embedSingleWithFallback(text2) {
3242
+ async embedSingleWithFallback(text3) {
3233
3243
  try {
3234
- return await this.embedSingle(text2);
3244
+ return await this.embedSingle(text3);
3235
3245
  } catch (error) {
3236
3246
  if (!this.isContextLengthError(error)) {
3237
3247
  throw error;
3238
3248
  }
3239
3249
  let lastError = error;
3240
- for (const truncated of this.buildTruncationCandidates(text2)) {
3250
+ for (const truncated of this.buildTruncationCandidates(text3)) {
3241
3251
  try {
3242
3252
  return await this.embedSingle(truncated);
3243
3253
  } catch (retryError) {
@@ -3250,7 +3260,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3250
3260
  throw lastError;
3251
3261
  }
3252
3262
  }
3253
- async embedSingle(text2) {
3263
+ async embedSingle(text3) {
3254
3264
  const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3255
3265
  method: "POST",
3256
3266
  headers: {
@@ -3258,7 +3268,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3258
3268
  },
3259
3269
  body: JSON.stringify({
3260
3270
  model: this.modelInfo.model,
3261
- prompt: text2,
3271
+ prompt: text3,
3262
3272
  truncate: false
3263
3273
  })
3264
3274
  });
@@ -3269,13 +3279,13 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3269
3279
  const data = await response.json();
3270
3280
  return {
3271
3281
  embedding: data.embedding,
3272
- tokensUsed: this.estimateTokens(text2)
3282
+ tokensUsed: this.estimateTokens(text3)
3273
3283
  };
3274
3284
  }
3275
3285
  async embedBatch(texts) {
3276
3286
  const results = [];
3277
- for (const text2 of texts) {
3278
- results.push(await this.embedSingleWithFallback(text2));
3287
+ for (const text3 of texts) {
3288
+ results.push(await this.embedSingleWithFallback(text3));
3279
3289
  }
3280
3290
  return {
3281
3291
  embeddings: results.map((r) => r.embedding),
@@ -3416,7 +3426,7 @@ var SiliconFlowReranker = class {
3416
3426
  var import_ignore = __toESM(require_ignore(), 1);
3417
3427
  import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
3418
3428
  import * as path8 from "path";
3419
- function createIgnoreFilter(projectRoot2) {
3429
+ function createIgnoreFilter(projectRoot3) {
3420
3430
  const ig = (0, import_ignore.default)();
3421
3431
  const defaultIgnores = [
3422
3432
  "node_modules",
@@ -3437,7 +3447,7 @@ function createIgnoreFilter(projectRoot2) {
3437
3447
  "**/*build*/**"
3438
3448
  ];
3439
3449
  ig.add(defaultIgnores);
3440
- const gitignorePath = path8.join(projectRoot2, ".gitignore");
3450
+ const gitignorePath = path8.join(projectRoot3, ".gitignore");
3441
3451
  if (existsSync6(gitignorePath)) {
3442
3452
  const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
3443
3453
  ig.add(gitignoreContent);
@@ -3459,13 +3469,13 @@ function matchGlob(filePath, pattern) {
3459
3469
  const regex = new RegExp(`^${regexPattern}$`);
3460
3470
  return regex.test(filePath);
3461
3471
  }
3462
- async function* walkDirectory(dir, projectRoot2, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3472
+ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3463
3473
  const entries = await fsPromises.readdir(dir, { withFileTypes: true });
3464
3474
  const filesInDir = [];
3465
3475
  const subdirs = [];
3466
3476
  for (const entry of entries) {
3467
3477
  const fullPath = path8.join(dir, entry.name);
3468
- const relativePath = path8.relative(projectRoot2, fullPath);
3478
+ const relativePath = path8.relative(projectRoot3, fullPath);
3469
3479
  if (isHiddenPathSegment(entry.name)) {
3470
3480
  if (entry.isDirectory()) {
3471
3481
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3514,14 +3524,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3514
3524
  yield f;
3515
3525
  }
3516
3526
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3517
- skipped.push({ path: path8.relative(projectRoot2, filesInDir[i].path), reason: "excluded" });
3527
+ skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3518
3528
  }
3519
3529
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3520
3530
  if (canRecurse) {
3521
3531
  for (const sub of subdirs) {
3522
3532
  yield* walkDirectory(
3523
3533
  sub.fullPath,
3524
- projectRoot2,
3534
+ projectRoot3,
3525
3535
  includePatterns,
3526
3536
  excludePatterns,
3527
3537
  ignoreFilter,
@@ -3533,14 +3543,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3533
3543
  }
3534
3544
  }
3535
3545
  }
3536
- async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3546
+ async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3537
3547
  const opts = walkOptions ?? { maxDepth: 5, maxFilesPerDirectory: 100 };
3538
- const ignoreFilter = createIgnoreFilter(projectRoot2);
3548
+ const ignoreFilter = createIgnoreFilter(projectRoot3);
3539
3549
  const files = [];
3540
3550
  const skipped = [];
3541
3551
  for await (const file of walkDirectory(
3542
- projectRoot2,
3543
- projectRoot2,
3552
+ projectRoot3,
3553
+ projectRoot3,
3544
3554
  includePatterns,
3545
3555
  excludePatterns,
3546
3556
  ignoreFilter,
@@ -3555,7 +3565,7 @@ async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxF
3555
3565
  const normalizedRoots = /* @__PURE__ */ new Set();
3556
3566
  for (const kbRoot of additionalRoots) {
3557
3567
  const resolved = path8.normalize(
3558
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot2, kbRoot)
3568
+ path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
3559
3569
  );
3560
3570
  normalizedRoots.add(resolved);
3561
3571
  }
@@ -3980,6 +3990,12 @@ function createMockNativeBinding() {
3980
3990
  constructor() {
3981
3991
  throw error;
3982
3992
  }
3993
+ static openReadOnly() {
3994
+ throw error;
3995
+ }
3996
+ static createEmptyReadOnly() {
3997
+ throw error;
3998
+ }
3983
3999
  close() {
3984
4000
  throw error;
3985
4001
  }
@@ -4083,6 +4099,12 @@ var VectorStore = class {
4083
4099
  load() {
4084
4100
  this.inner.load();
4085
4101
  }
4102
+ loadStrict() {
4103
+ this.inner.loadStrict();
4104
+ }
4105
+ hasFingerprint() {
4106
+ return this.inner.hasFingerprint();
4107
+ }
4086
4108
  count() {
4087
4109
  return this.inner.count();
4088
4110
  }
@@ -4121,8 +4143,8 @@ var VectorStore = class {
4121
4143
  var CHARS_PER_TOKEN = 4;
4122
4144
  var MAX_BATCH_TOKENS = 7500;
4123
4145
  var MAX_SINGLE_CHUNK_TOKENS = 2e3;
4124
- function estimateTokens(text2) {
4125
- return Math.ceil(text2.length / CHARS_PER_TOKEN);
4146
+ function estimateTokens(text3) {
4147
+ return Math.ceil(text3.length / CHARS_PER_TOKEN);
4126
4148
  }
4127
4149
  function getEmbeddingHeaderParts(chunk, filePath) {
4128
4150
  const parts = [];
@@ -4365,12 +4387,24 @@ var InvertedIndex = class {
4365
4387
  return this.inner.documentCount();
4366
4388
  }
4367
4389
  };
4368
- var Database = class {
4390
+ var Database = class _Database {
4369
4391
  inner;
4370
4392
  closed = false;
4371
4393
  constructor(dbPath) {
4372
4394
  this.inner = new native.Database(dbPath);
4373
4395
  }
4396
+ static fromNative(inner) {
4397
+ const database = Object.create(_Database.prototype);
4398
+ database.inner = inner;
4399
+ database.closed = false;
4400
+ return database;
4401
+ }
4402
+ static openReadOnly(dbPath) {
4403
+ return _Database.fromNative(native.Database.openReadOnly(dbPath));
4404
+ }
4405
+ static createEmptyReadOnly() {
4406
+ return _Database.fromNative(native.Database.createEmptyReadOnly());
4407
+ }
4374
4408
  throwIfClosed() {
4375
4409
  if (this.closed) {
4376
4410
  throw new Error("Database is closed");
@@ -4656,20 +4690,20 @@ function getErrorMessage(error) {
4656
4690
  return String(error);
4657
4691
  }
4658
4692
  async function getChangedFiles(opts) {
4659
- const { pr, branch, projectRoot: projectRoot2, baseBranch = "main" } = opts;
4693
+ const { pr, branch, projectRoot: projectRoot3, baseBranch = "main" } = opts;
4660
4694
  if (pr !== void 0) {
4661
- return getChangedFilesForPr(pr, projectRoot2, baseBranch);
4695
+ return getChangedFilesForPr(pr, projectRoot3, baseBranch);
4662
4696
  }
4663
- return getChangedFilesForBranch(branch, projectRoot2, baseBranch);
4697
+ return getChangedFilesForBranch(branch, projectRoot3, baseBranch);
4664
4698
  }
4665
- async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4699
+ async function getChangedFilesForPr(pr, projectRoot3, baseBranch) {
4666
4700
  let headRefName;
4667
4701
  let actualBaseBranch = baseBranch;
4668
4702
  try {
4669
4703
  const { stdout } = await execFileAsync(
4670
4704
  "gh",
4671
4705
  ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
4672
- { cwd: projectRoot2, timeout: 3e4 }
4706
+ { cwd: projectRoot3, timeout: 3e4 }
4673
4707
  );
4674
4708
  const data = JSON.parse(stdout);
4675
4709
  headRefName = data.headRefName;
@@ -4678,7 +4712,7 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4678
4712
  return {
4679
4713
  files: normalizeFiles(
4680
4714
  data.files.map((f) => f.path),
4681
- projectRoot2
4715
+ projectRoot3
4682
4716
  ),
4683
4717
  baseBranch: actualBaseBranch,
4684
4718
  source: "gh",
@@ -4695,49 +4729,49 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4695
4729
  `PR #${pr} returned no usable branch or file information.`
4696
4730
  );
4697
4731
  }
4698
- const result = await getChangedFilesForBranch(headRefName, projectRoot2, actualBaseBranch);
4732
+ const result = await getChangedFilesForBranch(headRefName, projectRoot3, actualBaseBranch);
4699
4733
  return { ...result, headRefName };
4700
4734
  }
4701
- async function getChangedFilesForBranch(branch, projectRoot2, baseBranch) {
4702
- const targetBranch = branch || await getCurrentBranch2(projectRoot2);
4703
- const mergeBase = await getMergeBase(projectRoot2, baseBranch, targetBranch);
4735
+ async function getChangedFilesForBranch(branch, projectRoot3, baseBranch) {
4736
+ const targetBranch = branch || await getCurrentBranch2(projectRoot3);
4737
+ const mergeBase = await getMergeBase(projectRoot3, baseBranch, targetBranch);
4704
4738
  const { stdout } = await execFileAsync(
4705
4739
  "git",
4706
4740
  ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
4707
- { cwd: projectRoot2, timeout: 3e4 }
4741
+ { cwd: projectRoot3, timeout: 3e4 }
4708
4742
  );
4709
4743
  return {
4710
- files: normalizeFiles(stdout.split("\n"), projectRoot2),
4744
+ files: normalizeFiles(stdout.split("\n"), projectRoot3),
4711
4745
  baseBranch,
4712
4746
  source: "git",
4713
4747
  headRefName: targetBranch
4714
4748
  };
4715
4749
  }
4716
- async function getCurrentBranch2(projectRoot2) {
4750
+ async function getCurrentBranch2(projectRoot3) {
4717
4751
  const { stdout } = await execFileAsync(
4718
4752
  "git",
4719
4753
  ["branch", "--show-current"],
4720
- { cwd: projectRoot2, timeout: 3e4 }
4754
+ { cwd: projectRoot3, timeout: 3e4 }
4721
4755
  );
4722
4756
  return stdout.trim() || "HEAD";
4723
4757
  }
4724
- async function getMergeBase(projectRoot2, baseBranch, branch) {
4758
+ async function getMergeBase(projectRoot3, baseBranch, branch) {
4725
4759
  const { stdout } = await execFileAsync(
4726
4760
  "git",
4727
4761
  ["merge-base", baseBranch, branch],
4728
- { cwd: projectRoot2, timeout: 3e4 }
4762
+ { cwd: projectRoot3, timeout: 3e4 }
4729
4763
  );
4730
4764
  return stdout.trim();
4731
4765
  }
4732
- function normalizeFiles(rawFiles, projectRoot2) {
4766
+ function normalizeFiles(rawFiles, projectRoot3) {
4733
4767
  const seen = /* @__PURE__ */ new Set();
4734
4768
  const result = [];
4735
4769
  for (const raw of rawFiles) {
4736
4770
  const trimmed = raw.trim();
4737
4771
  if (!trimmed) continue;
4738
- const absolute = path10.resolve(projectRoot2, trimmed);
4739
- const relative6 = path10.relative(projectRoot2, absolute);
4740
- const cleaned = relative6.startsWith("./") ? relative6.slice(2) : relative6;
4772
+ const absolute = path10.resolve(projectRoot3, trimmed);
4773
+ const relative7 = path10.relative(projectRoot3, absolute);
4774
+ const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
4741
4775
  if (!seen.has(cleaned)) {
4742
4776
  seen.add(cleaned);
4743
4777
  result.push(cleaned);
@@ -4746,8 +4780,483 @@ function normalizeFiles(rawFiles, projectRoot2) {
4746
4780
  return result;
4747
4781
  }
4748
4782
 
4783
+ // src/indexer/git-blame.ts
4784
+ import { execFile as execFile2 } from "child_process";
4785
+ import * as path11 from "path";
4786
+ import { promisify as promisify2 } from "util";
4787
+ var execFileAsync2 = promisify2(execFile2);
4788
+ function parseGitBlamePorcelain(output) {
4789
+ const commits = /* @__PURE__ */ new Map();
4790
+ let current;
4791
+ for (const line of output.split("\n")) {
4792
+ if (/^[0-9a-f]{40} /.test(line)) {
4793
+ const sha = line.slice(0, 40);
4794
+ current = commits.get(sha) ?? {
4795
+ sha,
4796
+ author: "",
4797
+ authorEmail: "",
4798
+ committedAt: 0,
4799
+ summary: "",
4800
+ lines: 0
4801
+ };
4802
+ commits.set(sha, current);
4803
+ continue;
4804
+ }
4805
+ if (!current) {
4806
+ continue;
4807
+ }
4808
+ if (line.startsWith("author ")) {
4809
+ current.author = line.slice("author ".length);
4810
+ } else if (line.startsWith("author-mail ")) {
4811
+ current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
4812
+ } else if (line.startsWith("author-time ")) {
4813
+ current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
4814
+ } else if (line.startsWith("summary ")) {
4815
+ current.summary = line.slice("summary ".length);
4816
+ } else if (line.startsWith(" ")) {
4817
+ current.lines += 1;
4818
+ }
4819
+ }
4820
+ return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4821
+ }
4822
+ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4823
+ const relativePath = path11.relative(projectRoot3, filePath);
4824
+ try {
4825
+ const { stdout } = await execFileAsync2(
4826
+ "git",
4827
+ ["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
4828
+ { cwd: projectRoot3, timeout: 3e4 }
4829
+ );
4830
+ return parseGitBlamePorcelain(stdout);
4831
+ } catch {
4832
+ return void 0;
4833
+ }
4834
+ }
4835
+
4836
+ // src/indexer/index-lock.ts
4837
+ import { randomUUID } from "crypto";
4838
+ import {
4839
+ existsSync as existsSync7,
4840
+ lstatSync,
4841
+ mkdirSync as mkdirSync2,
4842
+ readFileSync as readFileSync6,
4843
+ readdirSync as readdirSync2,
4844
+ realpathSync,
4845
+ renameSync,
4846
+ rmSync,
4847
+ writeFileSync as writeFileSync2
4848
+ } from "fs";
4849
+ import * as os4 from "os";
4850
+ import * as path12 from "path";
4851
+ var OWNER_FILE_NAME = "owner.json";
4852
+ var RECLAIM_DIRECTORY_NAME = "reclaiming";
4853
+ var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
4854
+ 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;
4855
+ var VALID_OPERATIONS = /* @__PURE__ */ new Set([
4856
+ "initialize",
4857
+ "index",
4858
+ "force-index",
4859
+ "clear",
4860
+ "health-check",
4861
+ "retry-failed-batches",
4862
+ "recovery"
4863
+ ]);
4864
+ var temporaryCounter = 0;
4865
+ function getErrorCode(error) {
4866
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
4867
+ }
4868
+ function retryTransientFilesystemOperation(operation) {
4869
+ let lastError;
4870
+ for (let attempt = 0; attempt < 3; attempt += 1) {
4871
+ try {
4872
+ operation();
4873
+ return;
4874
+ } catch (error) {
4875
+ lastError = error;
4876
+ const code = getErrorCode(error);
4877
+ if (code !== "EBUSY" && code !== "EPERM") throw error;
4878
+ if (attempt < 2) {
4879
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
4880
+ }
4881
+ }
4882
+ }
4883
+ throw lastError;
4884
+ }
4885
+ function parseOwner(value) {
4886
+ if (typeof value !== "object" || value === null) return null;
4887
+ const candidate = value;
4888
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4889
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4890
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4891
+ if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
4892
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4893
+ return candidate;
4894
+ }
4895
+ function parseReclaimOwner(value) {
4896
+ if (typeof value !== "object" || value === null) return null;
4897
+ const candidate = value;
4898
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4899
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4900
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4901
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4902
+ if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
4903
+ return candidate;
4904
+ }
4905
+ function readJsonDirectory(directoryPath, parser) {
4906
+ try {
4907
+ return parser(JSON.parse(readFileSync6(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4908
+ } catch {
4909
+ return null;
4910
+ }
4911
+ }
4912
+ function readDirectoryOwner(lockPath) {
4913
+ return readJsonDirectory(lockPath, parseOwner);
4914
+ }
4915
+ function readReclaimOwner(markerPath) {
4916
+ return readJsonDirectory(markerPath, parseReclaimOwner);
4917
+ }
4918
+ function readRecoveryOwner(markerPath) {
4919
+ return readDirectoryOwner(markerPath);
4920
+ }
4921
+ function readLegacyOwner(lockPath) {
4922
+ try {
4923
+ const parsed = JSON.parse(readFileSync6(lockPath, "utf-8"));
4924
+ if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
4925
+ if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
4926
+ return {
4927
+ pid: Number(parsed.pid),
4928
+ hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
4929
+ startedAt: parsed.startedAt,
4930
+ operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
4931
+ token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
4932
+ };
4933
+ } catch {
4934
+ return null;
4935
+ }
4936
+ }
4937
+ function getOwnerLiveness(owner) {
4938
+ if (owner.hostname !== os4.hostname()) return "unknown";
4939
+ try {
4940
+ process.kill(owner.pid, 0);
4941
+ return "alive";
4942
+ } catch (error) {
4943
+ const code = getErrorCode(error);
4944
+ if (code === "ESRCH") return "dead";
4945
+ if (code === "EPERM") return "alive";
4946
+ return "unknown";
4947
+ }
4948
+ }
4949
+ function sameOwner(left, right) {
4950
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
4951
+ }
4952
+ function sameReclaimOwner(left, right) {
4953
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
4954
+ }
4955
+ function publishJsonDirectory(finalPath, value) {
4956
+ const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
4957
+ mkdirSync2(candidatePath, { mode: 448 });
4958
+ try {
4959
+ writeFileSync2(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4960
+ encoding: "utf-8",
4961
+ flag: "wx",
4962
+ mode: 384
4963
+ });
4964
+ if (existsSync7(finalPath)) return false;
4965
+ try {
4966
+ renameSync(candidatePath, finalPath);
4967
+ return true;
4968
+ } catch (error) {
4969
+ if (existsSync7(finalPath)) return false;
4970
+ throw error;
4971
+ }
4972
+ } finally {
4973
+ if (existsSync7(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
4974
+ }
4975
+ }
4976
+ function createOwner(operation) {
4977
+ return {
4978
+ pid: process.pid,
4979
+ hostname: os4.hostname(),
4980
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4981
+ operation,
4982
+ token: randomUUID()
4983
+ };
4984
+ }
4985
+ function recoveryMarkerPath(indexPath, owner) {
4986
+ return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4987
+ }
4988
+ function publishRecoveryMarker(indexPath, owner) {
4989
+ const markerPath = recoveryMarkerPath(indexPath, owner);
4990
+ if (!publishJsonDirectory(markerPath, owner)) {
4991
+ const markerOwner = readRecoveryOwner(markerPath);
4992
+ if (!markerOwner || !sameOwner(markerOwner, owner)) {
4993
+ throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
4994
+ }
4995
+ }
4996
+ return markerPath;
4997
+ }
4998
+ function getPendingRecoveries(indexPath) {
4999
+ const recoveries = [];
5000
+ const markerNames = readdirSync2(indexPath).filter((name) => {
5001
+ if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
5002
+ return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
5003
+ }).sort();
5004
+ for (const markerName of markerNames) {
5005
+ const markerPath = path12.join(indexPath, markerName);
5006
+ const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
5007
+ let markerStats;
5008
+ try {
5009
+ markerStats = lstatSync(markerPath);
5010
+ } catch (error) {
5011
+ if (getErrorCode(error) === "ENOENT") continue;
5012
+ throw error;
5013
+ }
5014
+ if (!markerStats.isDirectory()) {
5015
+ throw new IndexLockContentionError(markerPath, null, "unknown-owner");
5016
+ }
5017
+ const owner = readRecoveryOwner(markerPath);
5018
+ if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
5019
+ throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
5020
+ }
5021
+ recoveries.push({ owner, markerPath });
5022
+ }
5023
+ recoveries.sort((left, right) => {
5024
+ const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
5025
+ return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
5026
+ });
5027
+ return recoveries;
5028
+ }
5029
+ function cleanupDeadPublicationCandidates(indexPath) {
5030
+ const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
5031
+ for (const entry of readdirSync2(indexPath)) {
5032
+ const match = candidatePattern.exec(entry);
5033
+ if (!match) continue;
5034
+ const pid = Number(match[1]);
5035
+ if (!Number.isInteger(pid) || pid <= 0) continue;
5036
+ if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
5037
+ rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
5038
+ }
5039
+ }
5040
+ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5041
+ const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5042
+ const marker = readReclaimOwner(markerPath);
5043
+ if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
5044
+ return false;
5045
+ }
5046
+ const currentOwner = readDirectoryOwner(lockPath);
5047
+ if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5048
+ return false;
5049
+ }
5050
+ const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${randomUUID()}`;
5051
+ try {
5052
+ renameSync(markerPath, claimedMarkerPath);
5053
+ } catch (error) {
5054
+ if (getErrorCode(error) === "ENOENT") return false;
5055
+ throw error;
5056
+ }
5057
+ const claimedMarker = readReclaimOwner(claimedMarkerPath);
5058
+ const ownerAfterClaim = readDirectoryOwner(lockPath);
5059
+ if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
5060
+ if (!existsSync7(markerPath) && existsSync7(claimedMarkerPath)) {
5061
+ renameSync(claimedMarkerPath, markerPath);
5062
+ }
5063
+ return false;
5064
+ }
5065
+ rmSync(claimedMarkerPath, { recursive: true, force: true });
5066
+ return true;
5067
+ }
5068
+ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5069
+ const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5070
+ const reclaimOwner = {
5071
+ pid: process.pid,
5072
+ hostname: os4.hostname(),
5073
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5074
+ token: randomUUID(),
5075
+ expectedOwnerToken: expectedOwner.token
5076
+ };
5077
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5078
+ if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
5079
+ if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
5080
+ return false;
5081
+ }
5082
+ try {
5083
+ const currentReclaimer = readReclaimOwner(reclaimPath);
5084
+ const currentOwner = readDirectoryOwner(lockPath);
5085
+ if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5086
+ return false;
5087
+ }
5088
+ publishRecoveryMarker(indexPath, expectedOwner);
5089
+ const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
5090
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
5091
+ if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
5092
+ return false;
5093
+ }
5094
+ const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
5095
+ renameSync(lockPath, quarantinePath);
5096
+ rmSync(quarantinePath, { recursive: true, force: true });
5097
+ return true;
5098
+ } catch (error) {
5099
+ if (getErrorCode(error) === "ENOENT") return false;
5100
+ throw error;
5101
+ }
5102
+ }
5103
+ var IndexLockContentionError = class extends Error {
5104
+ constructor(lockPath, owner, reason) {
5105
+ const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
5106
+ super(`Index mutation already in progress: ${ownerDescription}`);
5107
+ this.lockPath = lockPath;
5108
+ this.owner = owner;
5109
+ this.reason = reason;
5110
+ this.name = "IndexLockContentionError";
5111
+ }
5112
+ lockPath;
5113
+ owner;
5114
+ reason;
5115
+ code = "INDEX_BUSY";
5116
+ };
5117
+ function isIndexLockContentionError(error) {
5118
+ return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
5119
+ }
5120
+ function acquireIndexLock(indexPath, operation) {
5121
+ mkdirSync2(indexPath, { recursive: true });
5122
+ const canonicalIndexPath = realpathSync.native(indexPath);
5123
+ const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
5124
+ cleanupDeadPublicationCandidates(canonicalIndexPath);
5125
+ for (let attempt = 0; attempt < 6; attempt += 1) {
5126
+ const owner = createOwner(operation);
5127
+ if (publishJsonDirectory(lockPath, owner)) {
5128
+ const lease = {
5129
+ canonicalIndexPath,
5130
+ lockPath,
5131
+ owner,
5132
+ recoveries: []
5133
+ };
5134
+ try {
5135
+ lease.recoveries = getPendingRecoveries(canonicalIndexPath);
5136
+ return lease;
5137
+ } catch (error) {
5138
+ releaseIndexLock(lease);
5139
+ throw error;
5140
+ }
5141
+ }
5142
+ let stats;
5143
+ try {
5144
+ stats = lstatSync(lockPath);
5145
+ } catch (error) {
5146
+ if (getErrorCode(error) === "ENOENT") continue;
5147
+ throw error;
5148
+ }
5149
+ if (!stats.isDirectory()) {
5150
+ const legacyOwner = readLegacyOwner(lockPath);
5151
+ throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
5152
+ }
5153
+ const existingOwner = readDirectoryOwner(lockPath);
5154
+ if (!existingOwner) {
5155
+ throw new IndexLockContentionError(lockPath, null, "unknown-owner");
5156
+ }
5157
+ const liveness = getOwnerLiveness(existingOwner);
5158
+ if (liveness !== "dead") {
5159
+ throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
5160
+ }
5161
+ if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
5162
+ throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
5163
+ }
5164
+ }
5165
+ throw new IndexLockContentionError(lockPath, null, "reclaiming");
5166
+ }
5167
+ function releaseIndexLock(lease) {
5168
+ const currentOwner = readDirectoryOwner(lease.lockPath);
5169
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
5170
+ const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
5171
+ try {
5172
+ retryTransientFilesystemOperation(() => renameSync(lease.lockPath, releasePath));
5173
+ } catch (error) {
5174
+ if (getErrorCode(error) === "ENOENT") return false;
5175
+ throw error;
5176
+ }
5177
+ const claimedOwner = readDirectoryOwner(releasePath);
5178
+ if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
5179
+ if (!existsSync7(lease.lockPath) && existsSync7(releasePath)) {
5180
+ renameSync(releasePath, lease.lockPath);
5181
+ }
5182
+ return false;
5183
+ }
5184
+ try {
5185
+ retryTransientFilesystemOperation(() => rmSync(releasePath, { recursive: true, force: true }));
5186
+ } catch (error) {
5187
+ console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
5188
+ }
5189
+ return true;
5190
+ }
5191
+ async function withIndexLock(indexPath, operation, callback, options = {}) {
5192
+ const lease = acquireIndexLock(indexPath, operation);
5193
+ let result;
5194
+ let callbackError;
5195
+ let callbackFailed = false;
5196
+ try {
5197
+ result = await callback(lease);
5198
+ } catch (error) {
5199
+ callbackFailed = true;
5200
+ callbackError = error;
5201
+ }
5202
+ if (!callbackFailed && options.completeRecoveries !== false) {
5203
+ try {
5204
+ completeLeaseRecovery(lease);
5205
+ } catch (error) {
5206
+ callbackFailed = true;
5207
+ callbackError = error;
5208
+ }
5209
+ }
5210
+ let releaseError;
5211
+ try {
5212
+ if (!releaseIndexLock(lease)) {
5213
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5214
+ }
5215
+ } catch (error) {
5216
+ releaseError = error;
5217
+ }
5218
+ if (releaseError !== void 0) {
5219
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5220
+ throw releaseError;
5221
+ }
5222
+ if (callbackFailed) throw callbackError;
5223
+ return result;
5224
+ }
5225
+ function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5226
+ if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5227
+ temporaryCounter += 1;
5228
+ return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
5229
+ }
5230
+ function removeLeaseTemporaryPath(temporaryPath) {
5231
+ if (existsSync7(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
5232
+ }
5233
+ function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
5234
+ for (const targetPath of backupTargets) {
5235
+ const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
5236
+ if (!existsSync7(backupPath)) continue;
5237
+ if (existsSync7(targetPath)) rmSync(targetPath, { recursive: true, force: true });
5238
+ renameSync(backupPath, targetPath);
5239
+ }
5240
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
5241
+ for (const entry of readdirSync2(indexPath)) {
5242
+ if (!entry.includes(temporaryOwnerMarker)) continue;
5243
+ rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
5244
+ }
5245
+ }
5246
+ function completeLeaseRecovery(lease) {
5247
+ for (const recovery of lease.recoveries) {
5248
+ const markerOwner = readRecoveryOwner(recovery.markerPath);
5249
+ if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
5250
+ throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
5251
+ }
5252
+ }
5253
+ for (const recovery of lease.recoveries) {
5254
+ rmSync(recovery.markerPath, { recursive: true, force: true });
5255
+ }
5256
+ }
5257
+
4749
5258
  // src/indexer/index.ts
4750
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
5259
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4751
5260
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
4752
5261
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4753
5262
  "function_declaration",
@@ -4827,6 +5336,46 @@ function isSqliteCorruptionError(error) {
4827
5336
  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");
4828
5337
  }
4829
5338
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5339
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
5340
+ function metadataFromBlame(blame) {
5341
+ if (!blame) {
5342
+ return {};
5343
+ }
5344
+ return {
5345
+ blameSha: blame.sha,
5346
+ blameAuthor: blame.author,
5347
+ blameAuthorEmail: blame.authorEmail,
5348
+ blameCommittedAt: blame.committedAt,
5349
+ blameSummary: blame.summary
5350
+ };
5351
+ }
5352
+ function blameFromChunkData(chunk) {
5353
+ if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
5354
+ return void 0;
5355
+ }
5356
+ return {
5357
+ sha: chunk.blameSha,
5358
+ author: chunk.blameAuthor,
5359
+ authorEmail: chunk.blameAuthorEmail,
5360
+ committedAt: chunk.blameCommittedAt,
5361
+ summary: chunk.blameSummary
5362
+ };
5363
+ }
5364
+ function blameFromMetadata(metadata) {
5365
+ if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
5366
+ return void 0;
5367
+ }
5368
+ return {
5369
+ sha: metadata.blameSha,
5370
+ author: metadata.blameAuthor,
5371
+ authorEmail: metadata.blameAuthorEmail,
5372
+ committedAt: metadata.blameCommittedAt,
5373
+ summary: metadata.blameSummary
5374
+ };
5375
+ }
5376
+ function hasBlameMetadata(metadata) {
5377
+ return blameFromMetadata(metadata) !== void 0;
5378
+ }
4830
5379
  var INDEX_METADATA_VERSION = "1";
4831
5380
  var EMBEDDING_STRATEGY_VERSION = "2";
4832
5381
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
@@ -4870,9 +5419,9 @@ function normalizePendingChunk(rawChunk, maxChunkTokens) {
4870
5419
  };
4871
5420
  const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
4872
5421
  texts.push(
4873
- ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text2) => ({
4874
- text: text2,
4875
- tokenCount: estimateTokens(text2)
5422
+ ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text3) => ({
5423
+ text: text3,
5424
+ tokenCount: estimateTokens(text3)
4876
5425
  }))
4877
5426
  );
4878
5427
  } else {
@@ -4985,9 +5534,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
4985
5534
  return true;
4986
5535
  }
4987
5536
  function isPathWithinRoot(filePath, rootPath) {
4988
- const normalizedFilePath = path11.resolve(filePath);
4989
- const normalizedRoot = path11.resolve(rootPath);
4990
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5537
+ const normalizedFilePath = path13.resolve(filePath);
5538
+ const normalizedRoot = path13.resolve(rootPath);
5539
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
4991
5540
  }
4992
5541
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
4993
5542
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5103,11 +5652,11 @@ function setBoundedCache(cache, key, value) {
5103
5652
  }
5104
5653
  cache.set(key, value);
5105
5654
  }
5106
- function tokenizeTextForRanking(text2) {
5107
- if (!text2) {
5655
+ function tokenizeTextForRanking(text3) {
5656
+ if (!text3) {
5108
5657
  return /* @__PURE__ */ new Set();
5109
5658
  }
5110
- const lowered = text2.toLowerCase();
5659
+ const lowered = text3.toLowerCase();
5111
5660
  const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
5112
5661
  if (cache) {
5113
5662
  return cache;
@@ -5746,7 +6295,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
5746
6295
  chunkType,
5747
6296
  name: chunk.name ?? void 0,
5748
6297
  language: chunk.language,
5749
- hash: chunk.contentHash
6298
+ hash: chunk.contentHash,
6299
+ ...metadataFromBlame(blameFromChunkData(chunk))
5750
6300
  };
5751
6301
  const baselineScore = existing?.score ?? 0.5;
5752
6302
  candidateUnion.set(chunk.chunkId, {
@@ -5830,7 +6380,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
5830
6380
  chunkType,
5831
6381
  name: chunk.name ?? void 0,
5832
6382
  language: chunk.language,
5833
- hash: chunk.contentHash
6383
+ hash: chunk.contentHash,
6384
+ ...metadataFromBlame(blameFromChunkData(chunk))
5834
6385
  }
5835
6386
  });
5836
6387
  }
@@ -5999,6 +6550,21 @@ function matchesSearchFilters(candidate, options, minScore) {
5999
6550
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
6000
6551
  return false;
6001
6552
  }
6553
+ if (options?.blameAuthor) {
6554
+ const author = options.blameAuthor.toLowerCase();
6555
+ const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
6556
+ const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
6557
+ if (candidateAuthor !== author && candidateEmail !== author) return false;
6558
+ }
6559
+ if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
6560
+ return false;
6561
+ }
6562
+ if (options?.blameSince) {
6563
+ const sinceMs = Date.parse(options.blameSince);
6564
+ if (Number.isNaN(sinceMs)) return false;
6565
+ const committedAt = candidate.metadata.blameCommittedAt;
6566
+ if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
6567
+ }
6002
6568
  return true;
6003
6569
  }
6004
6570
  function unionCandidates(semanticCandidates, keywordCandidates) {
@@ -6036,26 +6602,133 @@ var Indexer = class {
6036
6602
  queryCacheTtlMs = 5 * 60 * 1e3;
6037
6603
  querySimilarityThreshold = 0.85;
6038
6604
  indexCompatibility = null;
6039
- indexingLockPath = "";
6040
- constructor(projectRoot2, config, host = "opencode") {
6041
- this.projectRoot = projectRoot2;
6605
+ activeIndexLease = null;
6606
+ initializationPromise = null;
6607
+ initializationMode = "none";
6608
+ readIssues = [];
6609
+ retiredDatabases = [];
6610
+ readerArtifactFingerprint = null;
6611
+ writerArtifactFingerprint = null;
6612
+ readerArtifactRetryAfter = /* @__PURE__ */ new Map();
6613
+ constructor(projectRoot3, config, host = "opencode") {
6614
+ this.projectRoot = projectRoot3;
6042
6615
  this.config = config;
6043
6616
  this.host = host;
6044
6617
  this.indexPath = this.getIndexPath();
6045
- this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6046
- this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6047
- this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
6618
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6619
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6048
6620
  this.logger = initializeLogger(config.debug);
6049
6621
  }
6050
6622
  getIndexPath() {
6051
6623
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6052
6624
  }
6625
+ isLocalProjectIndexPath() {
6626
+ const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6627
+ if (this.host !== "opencode") {
6628
+ localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6629
+ }
6630
+ return localProjectIndexPaths.some((localPath) => {
6631
+ if (!existsSync8(localPath) || !existsSync8(this.indexPath)) {
6632
+ return path13.resolve(this.indexPath) === path13.resolve(localPath);
6633
+ }
6634
+ const indexStats = statSync3(this.indexPath);
6635
+ const localStats = statSync3(localPath);
6636
+ return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6637
+ });
6638
+ }
6639
+ resetLoadedIndexState(retireDatabase = false) {
6640
+ if (this.database) {
6641
+ if (retireDatabase) {
6642
+ this.retiredDatabases.push(this.database);
6643
+ } else {
6644
+ this.database.close();
6645
+ }
6646
+ }
6647
+ this.store = null;
6648
+ this.invertedIndex = null;
6649
+ this.database = null;
6650
+ this.provider = null;
6651
+ this.configuredProviderInfo = null;
6652
+ this.reranker = null;
6653
+ this.indexCompatibility = null;
6654
+ this.initializationMode = "none";
6655
+ this.readIssues = [];
6656
+ this.readerArtifactFingerprint = null;
6657
+ this.writerArtifactFingerprint = null;
6658
+ this.readerArtifactRetryAfter.clear();
6659
+ this.fileHashCache.clear();
6660
+ }
6661
+ refreshLoadedIndexState() {
6662
+ if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
6663
+ this.store.load();
6664
+ this.invertedIndex.load();
6665
+ this.fileHashCache.clear();
6666
+ this.loadFileHashCache();
6667
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
6668
+ this.readIssues = [];
6669
+ this.readerArtifactRetryAfter.clear();
6670
+ }
6671
+ async withIndexMutationLease(operation, callback) {
6672
+ const lease = acquireIndexLock(this.indexPath, operation);
6673
+ this.indexPath = lease.canonicalIndexPath;
6674
+ this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6675
+ this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6676
+ this.activeIndexLease = lease;
6677
+ let result;
6678
+ let callbackError;
6679
+ let callbackFailed = false;
6680
+ try {
6681
+ result = await callback(lease.recoveries.map(({ owner }) => owner));
6682
+ } catch (error) {
6683
+ callbackFailed = true;
6684
+ callbackError = error;
6685
+ }
6686
+ if (!callbackFailed) {
6687
+ try {
6688
+ completeLeaseRecovery(lease);
6689
+ this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
6690
+ } catch (error) {
6691
+ callbackFailed = true;
6692
+ callbackError = error;
6693
+ }
6694
+ }
6695
+ let releaseError;
6696
+ try {
6697
+ if (!releaseIndexLock(lease)) {
6698
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
6699
+ this.writerArtifactFingerprint = null;
6700
+ if (this.activeIndexLease?.owner.token === lease.owner.token) {
6701
+ this.activeIndexLease = null;
6702
+ }
6703
+ } else if (this.activeIndexLease?.owner.token === lease.owner.token) {
6704
+ this.activeIndexLease = null;
6705
+ }
6706
+ } catch (error) {
6707
+ releaseError = error;
6708
+ this.writerArtifactFingerprint = null;
6709
+ if (!existsSync8(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6710
+ this.activeIndexLease = null;
6711
+ }
6712
+ }
6713
+ if (releaseError !== void 0) {
6714
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
6715
+ throw releaseError;
6716
+ }
6717
+ if (callbackFailed) throw callbackError;
6718
+ return result;
6719
+ }
6720
+ requireActiveLease() {
6721
+ if (!this.activeIndexLease) {
6722
+ throw new Error("Index mutation attempted without an active interprocess lease");
6723
+ }
6724
+ return this.activeIndexLease;
6725
+ }
6053
6726
  loadFileHashCache() {
6054
- if (!existsSync7(this.fileHashCachePath)) {
6727
+ if (!existsSync8(this.fileHashCachePath)) {
6055
6728
  return;
6056
6729
  }
6057
6730
  try {
6058
- const data = readFileSync6(this.fileHashCachePath, "utf-8");
6731
+ const data = readFileSync7(this.fileHashCachePath, "utf-8");
6059
6732
  const parsed = JSON.parse(data);
6060
6733
  this.fileHashCache = new Map(Object.entries(parsed));
6061
6734
  } catch (error) {
@@ -6075,15 +6748,26 @@ var Indexer = class {
6075
6748
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6076
6749
  }
6077
6750
  atomicWriteSync(targetPath, data) {
6078
- const tempPath = `${targetPath}.tmp`;
6079
- mkdirSync2(path11.dirname(targetPath), { recursive: true });
6080
- writeFileSync2(tempPath, data);
6081
- renameSync(tempPath, targetPath);
6751
+ const lease = this.requireActiveLease();
6752
+ const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6753
+ mkdirSync3(path13.dirname(targetPath), { recursive: true });
6754
+ try {
6755
+ writeFileSync3(tempPath, data);
6756
+ renameSync2(tempPath, targetPath);
6757
+ } finally {
6758
+ removeLeaseTemporaryPath(tempPath);
6759
+ }
6760
+ }
6761
+ saveInvertedIndex(invertedIndex) {
6762
+ this.atomicWriteSync(
6763
+ path13.join(this.indexPath, "inverted-index.json"),
6764
+ invertedIndex.serialize()
6765
+ );
6082
6766
  }
6083
6767
  getScopedRoots() {
6084
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6768
+ const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6085
6769
  for (const kbRoot of this.config.knowledgeBases) {
6086
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6770
+ roots.add(path13.resolve(this.projectRoot, kbRoot));
6087
6771
  }
6088
6772
  return Array.from(roots);
6089
6773
  }
@@ -6092,29 +6776,29 @@ var Indexer = class {
6092
6776
  if (this.config.scope !== "global") {
6093
6777
  return branchName;
6094
6778
  }
6095
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6779
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6096
6780
  return `${projectHash}:${branchName}`;
6097
6781
  }
6098
6782
  getBranchCatalogKeyFor(branchName) {
6099
6783
  if (this.config.scope !== "global") {
6100
6784
  return branchName;
6101
6785
  }
6102
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6786
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6103
6787
  return `${projectHash}:${branchName}`;
6104
6788
  }
6105
6789
  getLegacyBranchCatalogKey() {
6106
6790
  return this.currentBranch || "default";
6107
6791
  }
6108
6792
  getLegacyMigrationMetadataKey() {
6109
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6793
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6110
6794
  return `index.globalBranchMigration.${projectHash}`;
6111
6795
  }
6112
6796
  getProjectEmbeddingStrategyMetadataKey() {
6113
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6797
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6114
6798
  return `index.embeddingStrategyVersion.${projectHash}`;
6115
6799
  }
6116
6800
  getProjectForceReembedMetadataKey() {
6117
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6801
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6118
6802
  return `index.forceReembed.${projectHash}`;
6119
6803
  }
6120
6804
  hasProjectForceReembedPending() {
@@ -6208,7 +6892,7 @@ var Indexer = class {
6208
6892
  if (!this.database) {
6209
6893
  return { chunkIds, symbolIds };
6210
6894
  }
6211
- const projectRootPath = path11.resolve(this.projectRoot);
6895
+ const projectRootPath = path13.resolve(this.projectRoot);
6212
6896
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6213
6897
  ...Array.from(this.fileHashCache.keys()).filter(
6214
6898
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6231,7 +6915,7 @@ var Indexer = class {
6231
6915
  if (this.config.scope !== "global") {
6232
6916
  return this.getBranchCatalogCleanupKeys();
6233
6917
  }
6234
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6918
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6235
6919
  const keys = /* @__PURE__ */ new Set();
6236
6920
  const projectChunkIdSet = new Set(projectChunkIds);
6237
6921
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6315,7 +6999,7 @@ var Indexer = class {
6315
6999
  if (!this.database || this.config.scope !== "global") {
6316
7000
  return false;
6317
7001
  }
6318
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7002
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6319
7003
  const roots = this.getScopedRoots();
6320
7004
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6321
7005
  return this.database.getAllBranches().some(
@@ -6349,7 +7033,7 @@ var Indexer = class {
6349
7033
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6350
7034
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6351
7035
  ]);
6352
- const projectRootPath = path11.resolve(this.projectRoot);
7036
+ const projectRootPath = path13.resolve(this.projectRoot);
6353
7037
  const projectLocalFilePaths = new Set(
6354
7038
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6355
7039
  );
@@ -6411,34 +7095,27 @@ var Indexer = class {
6411
7095
  database.gcOrphanEmbeddings();
6412
7096
  database.gcOrphanChunks();
6413
7097
  store.save();
6414
- invertedIndex.save();
7098
+ this.saveInvertedIndex(invertedIndex);
6415
7099
  return {
6416
7100
  removedChunkIds: removedChunkIdList,
6417
7101
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6418
7102
  };
6419
7103
  }
6420
- checkForInterruptedIndexing() {
6421
- return existsSync7(this.indexingLockPath);
6422
- }
6423
- acquireIndexingLock() {
6424
- const lockData = {
6425
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6426
- pid: process.pid
6427
- };
6428
- writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
6429
- }
6430
- releaseIndexingLock() {
6431
- if (existsSync7(this.indexingLockPath)) {
6432
- unlinkSync(this.indexingLockPath);
7104
+ async recoverFromInterruptedIndexingUnlocked(owners) {
7105
+ for (const owner of owners) {
7106
+ this.logger.warn("Detected interrupted indexing session, recovering...", {
7107
+ pid: owner.pid,
7108
+ hostname: owner.hostname,
7109
+ operation: owner.operation,
7110
+ startedAt: owner.startedAt
7111
+ });
6433
7112
  }
6434
- }
6435
- async recoverFromInterruptedIndexing() {
6436
- this.logger.warn("Detected interrupted indexing session, recovering...");
6437
- if (existsSync7(this.fileHashCachePath)) {
6438
- unlinkSync(this.fileHashCachePath);
7113
+ if (this.config.scope === "global") {
7114
+ if (existsSync8(this.fileHashCachePath)) {
7115
+ unlinkSync(this.fileHashCachePath);
7116
+ }
7117
+ await this.healthCheckUnlocked();
6439
7118
  }
6440
- await this.healthCheck();
6441
- this.releaseIndexingLock();
6442
7119
  this.logger.info("Recovery complete, next index will re-process all files");
6443
7120
  }
6444
7121
  loadFailedBatches(maxChunkTokens) {
@@ -6454,10 +7131,10 @@ var Indexer = class {
6454
7131
  }
6455
7132
  }
6456
7133
  loadSerializedFailedBatches() {
6457
- if (!existsSync7(this.failedBatchesPath)) {
7134
+ if (!existsSync8(this.failedBatchesPath)) {
6458
7135
  return [];
6459
7136
  }
6460
- const data = readFileSync6(this.failedBatchesPath, "utf-8");
7137
+ const data = readFileSync7(this.failedBatchesPath, "utf-8");
6461
7138
  const parsed = JSON.parse(data);
6462
7139
  return parsed.map((batch) => {
6463
7140
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6474,7 +7151,7 @@ var Indexer = class {
6474
7151
  }
6475
7152
  saveFailedBatches(batches) {
6476
7153
  if (batches.length === 0) {
6477
- if (existsSync7(this.failedBatchesPath)) {
7154
+ if (existsSync8(this.failedBatchesPath)) {
6478
7155
  try {
6479
7156
  unlinkSync(this.failedBatchesPath);
6480
7157
  } catch {
@@ -6482,7 +7159,7 @@ var Indexer = class {
6482
7159
  }
6483
7160
  return;
6484
7161
  }
6485
- writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7162
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6486
7163
  }
6487
7164
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6488
7165
  const retryableById = /* @__PURE__ */ new Map();
@@ -6676,67 +7353,350 @@ var Indexer = class {
6676
7353
  return parts.join("\n");
6677
7354
  }
6678
7355
  async initialize() {
6679
- if (this.config.embeddingProvider === "custom") {
6680
- if (!this.config.customProvider) {
6681
- throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
6682
- }
6683
- this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
6684
- } else if (this.config.embeddingProvider === "auto") {
6685
- this.configuredProviderInfo = await tryDetectProvider();
6686
- } else {
6687
- this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
7356
+ if (this.initializationPromise) {
7357
+ await this.initializationPromise;
6688
7358
  }
6689
- if (!this.configuredProviderInfo) {
6690
- throw new Error(
6691
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
6692
- );
7359
+ if (this.isInitializedFor("reader")) {
7360
+ return;
6693
7361
  }
6694
- this.logger.info("Initializing indexer", {
6695
- provider: this.configuredProviderInfo.provider,
6696
- model: this.configuredProviderInfo.modelInfo.model,
6697
- scope: this.config.scope,
6698
- rerankerEnabled: this.config.reranker?.enabled ?? false
6699
- });
6700
- this.provider = createEmbeddingProvider(this.configuredProviderInfo);
6701
- if (this.config.reranker?.enabled) {
6702
- this.reranker = createReranker(this.config.reranker);
6703
- if (this.reranker.isAvailable()) {
6704
- this.logger.info("Reranker initialized", {
6705
- model: this.config.reranker.model,
6706
- baseUrl: this.config.reranker.baseUrl
6707
- });
7362
+ await this.initializeOnce("reader", [], { skipAutoGc: true });
7363
+ }
7364
+ async initializeOnce(mode, recoveredOwners, options) {
7365
+ if (this.initializationPromise) {
7366
+ await this.initializationPromise;
7367
+ if (this.isInitializedFor(mode)) {
7368
+ return;
6708
7369
  }
7370
+ return this.initializeOnce(mode, recoveredOwners, options);
6709
7371
  }
6710
- await fsPromises2.mkdir(this.indexPath, { recursive: true });
6711
- const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6712
- const storePath = path11.join(this.indexPath, "vectors");
6713
- this.store = new VectorStore(storePath, dimensions);
6714
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6715
- if (existsSync7(indexFilePath)) {
6716
- this.store.load();
6717
- }
6718
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6719
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6720
- try {
6721
- this.invertedIndex.load();
6722
- } catch {
6723
- if (existsSync7(invertedIndexPath)) {
6724
- await fsPromises2.unlink(invertedIndexPath);
7372
+ if (this.isInitializedFor(mode)) {
7373
+ return;
7374
+ }
7375
+ const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
7376
+ this.resetLoadedIndexState();
7377
+ throw error;
7378
+ }).finally(() => {
7379
+ if (this.initializationPromise === initialization) {
7380
+ this.initializationPromise = null;
6725
7381
  }
6726
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
7382
+ });
7383
+ this.initializationPromise = initialization;
7384
+ await initialization;
7385
+ }
7386
+ isInitializedFor(mode) {
7387
+ const hasState = Boolean(
7388
+ this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
7389
+ );
7390
+ if (!hasState) {
7391
+ return false;
6727
7392
  }
6728
- const dbPath = path11.join(this.indexPath, "codebase.db");
6729
- let dbIsNew = !existsSync7(dbPath);
7393
+ return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
7394
+ }
7395
+ recordReadIssue(component, message, error) {
7396
+ this.readIssues.push(this.createReadIssue(component, message));
7397
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7398
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7399
+ }
7400
+ createReadIssue(component, message) {
7401
+ return {
7402
+ component,
7403
+ message,
7404
+ blocking: component !== "keyword"
7405
+ };
7406
+ }
7407
+ getVectorReadIssueMessage() {
7408
+ if (this.config.scope === "global") {
7409
+ return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
7410
+ }
7411
+ if (!this.isLocalProjectIndexPath()) {
7412
+ 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.";
7413
+ }
7414
+ 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.";
7415
+ }
7416
+ getKeywordReadIssueMessage() {
7417
+ if (this.config.scope === "global") {
7418
+ 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.";
7419
+ }
7420
+ if (!this.isLocalProjectIndexPath()) {
7421
+ 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.";
7422
+ }
7423
+ 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.";
7424
+ }
7425
+ getDatabaseReadIssueMessage() {
7426
+ if (this.config.scope === "global") {
7427
+ return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
7428
+ }
7429
+ if (!this.isLocalProjectIndexPath()) {
7430
+ 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.";
7431
+ }
7432
+ return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
7433
+ }
7434
+ getReaderFileFingerprint(filePath, identityOnly = false) {
6730
7435
  try {
6731
- this.database = new Database(dbPath);
7436
+ const stats = statSync3(filePath);
7437
+ if (identityOnly) {
7438
+ return `${stats.dev}:${stats.ino}`;
7439
+ }
7440
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
6732
7441
  } catch (error) {
6733
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6734
- throw error;
7442
+ return `unavailable:${getErrorMessage2(error)}`;
7443
+ }
7444
+ }
7445
+ captureReaderArtifactFingerprint() {
7446
+ const storePath = path13.join(this.indexPath, "vectors");
7447
+ return {
7448
+ vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7449
+ keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7450
+ database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7451
+ databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7452
+ };
7453
+ }
7454
+ refreshReaderArtifacts() {
7455
+ if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
7456
+ return;
7457
+ }
7458
+ const previousFingerprint = this.readerArtifactFingerprint;
7459
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7460
+ const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
7461
+ const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
7462
+ const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
7463
+ const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
7464
+ const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
7465
+ const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7466
+ if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
7467
+ return;
7468
+ }
7469
+ const setIssue = (component, message, error) => {
7470
+ if (!issues.has(component)) {
7471
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7472
+ }
7473
+ issues.set(component, this.createReadIssue(component, message));
7474
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7475
+ };
7476
+ const storePath = path13.join(this.indexPath, "vectors");
7477
+ const vectorMetadataPath = `${storePath}.meta.json`;
7478
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7479
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7480
+ if (vectorsChanged || retryDue("vectors")) {
7481
+ const vectorStoreExists = existsSync8(storePath);
7482
+ const vectorMetadataExists = existsSync8(vectorMetadataPath);
7483
+ if (vectorStoreExists && vectorMetadataExists) {
7484
+ try {
7485
+ const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
7486
+ store.loadStrict();
7487
+ this.store = store;
7488
+ issues.delete("vectors");
7489
+ this.readerArtifactRetryAfter.delete("vectors");
7490
+ } catch (error) {
7491
+ setIssue("vectors", this.getVectorReadIssueMessage(), error);
7492
+ }
7493
+ } else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
7494
+ setIssue("vectors", this.getVectorReadIssueMessage());
7495
+ }
7496
+ }
7497
+ if (keywordChanged || retryDue("keyword") || !existsSync8(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7498
+ if (existsSync8(invertedIndexPath)) {
7499
+ try {
7500
+ const invertedIndex = new InvertedIndex(invertedIndexPath);
7501
+ invertedIndex.load();
7502
+ this.invertedIndex = invertedIndex;
7503
+ issues.delete("keyword");
7504
+ this.readerArtifactRetryAfter.delete("keyword");
7505
+ } catch (error) {
7506
+ setIssue("keyword", this.getKeywordReadIssueMessage(), error);
7507
+ }
7508
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
7509
+ setIssue("keyword", this.getKeywordReadIssueMessage());
7510
+ }
7511
+ }
7512
+ if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7513
+ if (existsSync8(dbPath)) {
7514
+ try {
7515
+ const database = Database.openReadOnly(dbPath);
7516
+ if (this.database) {
7517
+ this.retiredDatabases.push(this.database);
7518
+ }
7519
+ this.database = database;
7520
+ issues.delete("database");
7521
+ this.readerArtifactRetryAfter.delete("database");
7522
+ } catch (error) {
7523
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7524
+ }
7525
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
7526
+ setIssue("database", this.getDatabaseReadIssueMessage());
7527
+ }
7528
+ }
7529
+ if (!issues.has("database")) {
7530
+ try {
7531
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7532
+ } catch (error) {
7533
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7534
+ }
7535
+ }
7536
+ this.readIssues = Array.from(issues.values());
7537
+ this.readerArtifactFingerprint = currentFingerprint;
7538
+ }
7539
+ refreshInactiveWriterArtifacts() {
7540
+ if (this.initializationMode !== "writer" || this.activeIndexLease) {
7541
+ return true;
7542
+ }
7543
+ const previousFingerprint = this.writerArtifactFingerprint;
7544
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7545
+ const retryDue = this.readIssues.some(
7546
+ (issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
7547
+ );
7548
+ const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7549
+ if (!artifactsChanged && !retryDue) {
7550
+ return true;
7551
+ }
7552
+ if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
7553
+ return false;
7554
+ }
7555
+ this.initializationMode = "reader";
7556
+ this.readerArtifactFingerprint = previousFingerprint;
7557
+ try {
7558
+ this.refreshReaderArtifacts();
7559
+ this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
7560
+ } finally {
7561
+ this.readerArtifactFingerprint = null;
7562
+ this.initializationMode = "writer";
7563
+ }
7564
+ return true;
7565
+ }
7566
+ async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
7567
+ if (mode === "writer") {
7568
+ this.requireActiveLease();
7569
+ }
7570
+ this.readIssues = [];
7571
+ this.readerArtifactRetryAfter.clear();
7572
+ if (this.config.embeddingProvider === "custom") {
7573
+ if (!this.config.customProvider) {
7574
+ throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
7575
+ }
7576
+ this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
7577
+ } else if (this.config.embeddingProvider === "auto") {
7578
+ this.configuredProviderInfo = await tryDetectProvider();
7579
+ } else {
7580
+ this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
7581
+ }
7582
+ if (!this.configuredProviderInfo) {
7583
+ throw new Error(
7584
+ "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
7585
+ );
7586
+ }
7587
+ this.logger.info("Initializing indexer", {
7588
+ provider: this.configuredProviderInfo.provider,
7589
+ model: this.configuredProviderInfo.modelInfo.model,
7590
+ scope: this.config.scope,
7591
+ rerankerEnabled: this.config.reranker?.enabled ?? false
7592
+ });
7593
+ this.provider = createEmbeddingProvider(this.configuredProviderInfo);
7594
+ if (this.config.reranker?.enabled) {
7595
+ this.reranker = createReranker(this.config.reranker);
7596
+ if (this.reranker.isAvailable()) {
7597
+ this.logger.info("Reranker initialized", {
7598
+ model: this.config.reranker.model,
7599
+ baseUrl: this.config.reranker.baseUrl
7600
+ });
7601
+ }
7602
+ }
7603
+ const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7604
+ const storePath = path13.join(this.indexPath, "vectors");
7605
+ const vectorMetadataPath = `${storePath}.meta.json`;
7606
+ const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7607
+ const dbPath = path13.join(this.indexPath, "codebase.db");
7608
+ let dbIsNew = !existsSync8(dbPath);
7609
+ const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7610
+ if (mode === "writer") {
7611
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
7612
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7613
+ throw new Error(
7614
+ "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
7615
+ );
7616
+ }
7617
+ for (const recoveredOwner of recoveredOwners) {
7618
+ recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
7619
+ storePath,
7620
+ `${storePath}.meta.json`
7621
+ ]);
7622
+ }
7623
+ if (recoveredOwners.length > 0 && this.config.scope === "project") {
7624
+ await this.resetLocalIndexArtifacts();
6735
7625
  }
6736
7626
  this.store = new VectorStore(storePath, dimensions);
7627
+ if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
7628
+ this.store.load();
7629
+ }
7630
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7631
+ try {
7632
+ this.invertedIndex.load();
7633
+ } catch {
7634
+ if (existsSync8(invertedIndexPath)) {
7635
+ await fsPromises2.unlink(invertedIndexPath);
7636
+ }
7637
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7638
+ }
7639
+ try {
7640
+ this.database = new Database(dbPath);
7641
+ } catch (error) {
7642
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7643
+ throw error;
7644
+ }
7645
+ this.store = new VectorStore(storePath, dimensions);
7646
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7647
+ this.database = new Database(dbPath);
7648
+ dbIsNew = true;
7649
+ }
7650
+ } else {
7651
+ this.store = new VectorStore(storePath, dimensions);
7652
+ const vectorStoreExists = existsSync8(storePath);
7653
+ const vectorMetadataExists = existsSync8(vectorMetadataPath);
7654
+ const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7655
+ if (vectorStoreExists !== vectorMetadataExists) {
7656
+ this.recordReadIssue("vectors", vectorReadFailureMessage);
7657
+ } else if (vectorStoreExists) {
7658
+ try {
7659
+ this.store.loadStrict();
7660
+ } catch (error) {
7661
+ this.recordReadIssue("vectors", vectorReadFailureMessage, error);
7662
+ this.store = new VectorStore(storePath, dimensions);
7663
+ }
7664
+ }
6737
7665
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6738
- this.database = new Database(dbPath);
6739
- dbIsNew = true;
7666
+ if (existsSync8(invertedIndexPath)) {
7667
+ try {
7668
+ this.invertedIndex.load();
7669
+ } catch (error) {
7670
+ this.recordReadIssue(
7671
+ "keyword",
7672
+ this.getKeywordReadIssueMessage(),
7673
+ error
7674
+ );
7675
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7676
+ }
7677
+ } else if (this.store.count() > 0) {
7678
+ this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7679
+ }
7680
+ if (existsSync8(dbPath)) {
7681
+ try {
7682
+ this.database = Database.openReadOnly(dbPath);
7683
+ } catch (error) {
7684
+ this.recordReadIssue(
7685
+ "database",
7686
+ this.getDatabaseReadIssueMessage(),
7687
+ error
7688
+ );
7689
+ this.database = Database.createEmptyReadOnly();
7690
+ }
7691
+ } else {
7692
+ this.database = Database.createEmptyReadOnly();
7693
+ if (this.store.count() > 0) {
7694
+ this.recordReadIssue(
7695
+ "database",
7696
+ `Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
7697
+ );
7698
+ }
7699
+ }
6740
7700
  }
6741
7701
  if (isGitRepo(this.projectRoot)) {
6742
7702
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6750,10 +7710,10 @@ var Indexer = class {
6750
7710
  this.baseBranch = "default";
6751
7711
  this.logger.branch("debug", "Not a git repository, using default branch");
6752
7712
  }
6753
- if (this.checkForInterruptedIndexing()) {
6754
- await this.recoverFromInterruptedIndexing();
7713
+ if (mode === "writer" && recoveredOwners.length > 0) {
7714
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6755
7715
  }
6756
- if (dbIsNew && this.store.count() > 0) {
7716
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6757
7717
  this.migrateFromLegacyIndex();
6758
7718
  }
6759
7719
  this.loadFileHashCache();
@@ -6765,9 +7725,11 @@ var Indexer = class {
6765
7725
  configuredProviderInfo: this.configuredProviderInfo
6766
7726
  });
6767
7727
  }
6768
- if (this.config.indexing.autoGc) {
7728
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6769
7729
  await this.maybeRunAutoGc();
6770
7730
  }
7731
+ this.initializationMode = mode;
7732
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6771
7733
  }
6772
7734
  async maybeRunAutoGc() {
6773
7735
  if (!this.database) return;
@@ -6784,7 +7746,7 @@ var Indexer = class {
6784
7746
  }
6785
7747
  }
6786
7748
  if (shouldRunGc) {
6787
- const result = await this.healthCheck();
7749
+ const result = await this.healthCheckUnlocked();
6788
7750
  if (result.warning) {
6789
7751
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
6790
7752
  } else {
@@ -6806,7 +7768,7 @@ var Indexer = class {
6806
7768
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6807
7769
  return {
6808
7770
  resetCorruptedIndex: true,
6809
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7771
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
6810
7772
  };
6811
7773
  }
6812
7774
  throw error;
@@ -6821,28 +7783,29 @@ var Indexer = class {
6821
7783
  return;
6822
7784
  }
6823
7785
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6824
- const storeBasePath = path11.join(this.indexPath, "vectors");
6825
- const storeIndexPath = `${storeBasePath}.usearch`;
7786
+ const storeBasePath = path13.join(this.indexPath, "vectors");
7787
+ const storeIndexPath = storeBasePath;
6826
7788
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6827
- const backupIndexPath = `${storeIndexPath}.bak`;
6828
- const backupMetadataPath = `${storeMetadataPath}.bak`;
7789
+ const lease = this.requireActiveLease();
7790
+ const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
7791
+ const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
6829
7792
  let backedUpIndex = false;
6830
7793
  let backedUpMetadata = false;
6831
7794
  let rebuiltCount = 0;
6832
7795
  let skippedCount = 0;
6833
- if (existsSync7(backupIndexPath)) {
7796
+ if (existsSync8(backupIndexPath)) {
6834
7797
  unlinkSync(backupIndexPath);
6835
7798
  }
6836
- if (existsSync7(backupMetadataPath)) {
7799
+ if (existsSync8(backupMetadataPath)) {
6837
7800
  unlinkSync(backupMetadataPath);
6838
7801
  }
6839
7802
  try {
6840
- if (existsSync7(storeIndexPath)) {
6841
- renameSync(storeIndexPath, backupIndexPath);
7803
+ if (existsSync8(storeIndexPath)) {
7804
+ renameSync2(storeIndexPath, backupIndexPath);
6842
7805
  backedUpIndex = true;
6843
7806
  }
6844
- if (existsSync7(storeMetadataPath)) {
6845
- renameSync(storeMetadataPath, backupMetadataPath);
7807
+ if (existsSync8(storeMetadataPath)) {
7808
+ renameSync2(storeMetadataPath, backupMetadataPath);
6846
7809
  backedUpMetadata = true;
6847
7810
  }
6848
7811
  store.clear();
@@ -6862,10 +7825,10 @@ var Indexer = class {
6862
7825
  rebuiltCount += 1;
6863
7826
  }
6864
7827
  store.save();
6865
- if (backedUpIndex && existsSync7(backupIndexPath)) {
7828
+ if (backedUpIndex && existsSync8(backupIndexPath)) {
6866
7829
  unlinkSync(backupIndexPath);
6867
7830
  }
6868
- if (backedUpMetadata && existsSync7(backupMetadataPath)) {
7831
+ if (backedUpMetadata && existsSync8(backupMetadataPath)) {
6869
7832
  unlinkSync(backupMetadataPath);
6870
7833
  }
6871
7834
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -6878,17 +7841,17 @@ var Indexer = class {
6878
7841
  store.clear();
6879
7842
  } catch {
6880
7843
  }
6881
- if (existsSync7(storeIndexPath)) {
7844
+ if (existsSync8(storeIndexPath)) {
6882
7845
  unlinkSync(storeIndexPath);
6883
7846
  }
6884
- if (existsSync7(storeMetadataPath)) {
7847
+ if (existsSync8(storeMetadataPath)) {
6885
7848
  unlinkSync(storeMetadataPath);
6886
7849
  }
6887
- if (backedUpIndex && existsSync7(backupIndexPath)) {
6888
- renameSync(backupIndexPath, storeIndexPath);
7850
+ if (backedUpIndex && existsSync8(backupIndexPath)) {
7851
+ renameSync2(backupIndexPath, storeIndexPath);
6889
7852
  }
6890
- if (backedUpMetadata && existsSync7(backupMetadataPath)) {
6891
- renameSync(backupMetadataPath, storeMetadataPath);
7853
+ if (backedUpMetadata && existsSync8(backupMetadataPath)) {
7854
+ renameSync2(backupMetadataPath, storeMetadataPath);
6892
7855
  }
6893
7856
  if (backedUpIndex || backedUpMetadata) {
6894
7857
  store.load();
@@ -6902,11 +7865,37 @@ var Indexer = class {
6902
7865
  }
6903
7866
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
6904
7867
  }
7868
+ async resetLocalIndexArtifacts() {
7869
+ this.store = null;
7870
+ this.invertedIndex = null;
7871
+ this.database?.close();
7872
+ this.database = null;
7873
+ this.indexCompatibility = null;
7874
+ this.initializationMode = "none";
7875
+ this.readIssues = [];
7876
+ this.readerArtifactFingerprint = null;
7877
+ this.writerArtifactFingerprint = null;
7878
+ this.readerArtifactRetryAfter.clear();
7879
+ this.fileHashCache.clear();
7880
+ const resetPaths = [
7881
+ path13.join(this.indexPath, "codebase.db"),
7882
+ path13.join(this.indexPath, "codebase.db-shm"),
7883
+ path13.join(this.indexPath, "codebase.db-wal"),
7884
+ path13.join(this.indexPath, "vectors"),
7885
+ path13.join(this.indexPath, "vectors.usearch"),
7886
+ path13.join(this.indexPath, "vectors.meta.json"),
7887
+ path13.join(this.indexPath, "inverted-index.json"),
7888
+ path13.join(this.indexPath, "file-hashes.json"),
7889
+ path13.join(this.indexPath, "failed-batches.json")
7890
+ ];
7891
+ await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
7892
+ await fsPromises2.mkdir(this.indexPath, { recursive: true });
7893
+ }
6905
7894
  async tryResetCorruptedIndex(stage, error) {
6906
7895
  if (!isSqliteCorruptionError(error)) {
6907
7896
  return false;
6908
7897
  }
6909
- const dbPath = path11.join(this.indexPath, "codebase.db");
7898
+ const dbPath = path13.join(this.indexPath, "codebase.db");
6910
7899
  const warning = this.getCorruptedIndexWarning(dbPath);
6911
7900
  const errorMessage = getErrorMessage2(error);
6912
7901
  if (this.config.scope === "global") {
@@ -6922,30 +7911,7 @@ var Indexer = class {
6922
7911
  dbPath,
6923
7912
  error: errorMessage
6924
7913
  });
6925
- this.store = null;
6926
- this.invertedIndex = null;
6927
- this.database?.close();
6928
- this.database = null;
6929
- this.indexCompatibility = null;
6930
- this.fileHashCache.clear();
6931
- const resetPaths = [
6932
- path11.join(this.indexPath, "codebase.db"),
6933
- path11.join(this.indexPath, "codebase.db-shm"),
6934
- path11.join(this.indexPath, "codebase.db-wal"),
6935
- path11.join(this.indexPath, "vectors.usearch"),
6936
- path11.join(this.indexPath, "inverted-index.json"),
6937
- path11.join(this.indexPath, "file-hashes.json"),
6938
- path11.join(this.indexPath, "failed-batches.json"),
6939
- path11.join(this.indexPath, "indexing.lock"),
6940
- path11.join(this.indexPath, "vectors")
6941
- ];
6942
- await Promise.all(resetPaths.map(async (targetPath) => {
6943
- try {
6944
- await fsPromises2.rm(targetPath, { recursive: true, force: true });
6945
- } catch {
6946
- }
6947
- }));
6948
- await fsPromises2.mkdir(this.indexPath, { recursive: true });
7914
+ await this.resetLocalIndexArtifacts();
6949
7915
  return true;
6950
7916
  }
6951
7917
  migrateFromLegacyIndex() {
@@ -7064,8 +8030,62 @@ var Indexer = class {
7064
8030
  return this.indexCompatibility;
7065
8031
  }
7066
8032
  async ensureInitialized() {
8033
+ let initializedReader = false;
8034
+ while (true) {
8035
+ if (this.initializationPromise) {
8036
+ await this.initializationPromise;
8037
+ }
8038
+ if (!this.isInitializedFor("reader")) {
8039
+ await this.initialize();
8040
+ initializedReader = true;
8041
+ continue;
8042
+ }
8043
+ if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
8044
+ if (!this.refreshInactiveWriterArtifacts()) {
8045
+ this.resetLoadedIndexState(true);
8046
+ await this.initialize();
8047
+ initializedReader = true;
8048
+ continue;
8049
+ }
8050
+ }
8051
+ if (this.initializationMode === "reader" && !initializedReader) {
8052
+ this.refreshReaderArtifacts();
8053
+ }
8054
+ const state = this.requireLoadedIndexState();
8055
+ return {
8056
+ ...state,
8057
+ readIssues: [...this.readIssues],
8058
+ compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
8059
+ };
8060
+ }
8061
+ }
8062
+ async ensureInitializedUnlocked(recoveredOwners = []) {
8063
+ this.requireActiveLease();
8064
+ if (this.initializationPromise) {
8065
+ await this.initializationPromise;
8066
+ }
8067
+ if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
8068
+ const retireReaderDatabase = this.initializationMode === "reader";
8069
+ this.resetLoadedIndexState(retireReaderDatabase);
8070
+ await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
8071
+ } else {
8072
+ this.refreshLoadedIndexState();
8073
+ }
8074
+ if (this.config.indexing.autoGc) {
8075
+ await this.maybeRunAutoGc();
8076
+ }
8077
+ return this.requireLoadedIndexState();
8078
+ }
8079
+ requireReadableComponents(readIssues, ...components) {
8080
+ const componentSet = new Set(components);
8081
+ const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
8082
+ if (issues.length > 0) {
8083
+ throw new Error(issues.map((issue) => issue.message).join(" "));
8084
+ }
8085
+ }
8086
+ requireLoadedIndexState() {
7067
8087
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7068
- await this.initialize();
8088
+ throw new Error("Index state is not initialized");
7069
8089
  }
7070
8090
  return {
7071
8091
  store: this.store,
@@ -7089,7 +8109,12 @@ var Indexer = class {
7089
8109
  return createCostEstimate(files, configuredProviderInfo);
7090
8110
  }
7091
8111
  async index(onProgress) {
7092
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8112
+ return this.withIndexMutationLease("index", async (recoveredOwners) => {
8113
+ return this.indexUnlocked(onProgress, recoveredOwners);
8114
+ });
8115
+ }
8116
+ async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
8117
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
7093
8118
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7094
8119
  const branchCatalogKey = this.getBranchCatalogKey();
7095
8120
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7099,7 +8124,6 @@ var Indexer = class {
7099
8124
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7100
8125
  );
7101
8126
  }
7102
- this.acquireIndexingLock();
7103
8127
  this.logger.recordIndexingStart();
7104
8128
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7105
8129
  const startTime = Date.now();
@@ -7174,6 +8198,7 @@ var Indexer = class {
7174
8198
  this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
7175
8199
  const existingChunks = /* @__PURE__ */ new Map();
7176
8200
  const existingChunksByFile = /* @__PURE__ */ new Map();
8201
+ const existingMetadataById = /* @__PURE__ */ new Map();
7177
8202
  for (const { key, metadata } of store.getAllMetadata()) {
7178
8203
  if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
7179
8204
  continue;
@@ -7182,6 +8207,7 @@ var Indexer = class {
7182
8207
  continue;
7183
8208
  }
7184
8209
  existingChunks.set(key, metadata.hash);
8210
+ existingMetadataById.set(key, metadata);
7185
8211
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7186
8212
  fileChunks.add(key);
7187
8213
  existingChunksByFile.set(metadata.filePath, fileChunks);
@@ -7189,6 +8215,8 @@ var Indexer = class {
7189
8215
  const currentChunkIds = /* @__PURE__ */ new Set();
7190
8216
  const currentFilePaths = /* @__PURE__ */ new Set();
7191
8217
  const pendingChunks = [];
8218
+ const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
8219
+ let backfilledBlameMetadata = false;
7192
8220
  for (const filePath of unchangedFilePaths) {
7193
8221
  currentFilePaths.add(filePath);
7194
8222
  const fileChunks = existingChunksByFile.get(filePath);
@@ -7199,10 +8227,52 @@ var Indexer = class {
7199
8227
  }
7200
8228
  }
7201
8229
  const chunkDataBatch = [];
8230
+ if (gitBlameEnabled) {
8231
+ const backfillItems = [];
8232
+ for (const chunkId of currentChunkIds) {
8233
+ const metadata = existingMetadataById.get(chunkId);
8234
+ if (!metadata || hasBlameMetadata(metadata)) {
8235
+ continue;
8236
+ }
8237
+ const chunk = database.getChunk(chunkId);
8238
+ if (!chunk) {
8239
+ continue;
8240
+ }
8241
+ const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
8242
+ const blameMetadata = metadataFromBlame(blame);
8243
+ if (!blameMetadata.blameSha) {
8244
+ continue;
8245
+ }
8246
+ chunkDataBatch.push({
8247
+ ...chunk,
8248
+ blameSha: blameMetadata.blameSha,
8249
+ blameAuthor: blameMetadata.blameAuthor,
8250
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8251
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8252
+ blameSummary: blameMetadata.blameSummary
8253
+ });
8254
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
8255
+ if (!embeddingBuffer) {
8256
+ continue;
8257
+ }
8258
+ backfillItems.push({
8259
+ id: chunkId,
8260
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
8261
+ metadata: {
8262
+ ...metadata,
8263
+ ...blameMetadata
8264
+ }
8265
+ });
8266
+ }
8267
+ if (backfillItems.length > 0) {
8268
+ store.addBatch(backfillItems);
8269
+ backfilledBlameMetadata = true;
8270
+ }
8271
+ }
7202
8272
  for (const parsed of parsedFiles) {
7203
8273
  currentFilePaths.add(parsed.path);
7204
8274
  if (parsed.chunks.length === 0) {
7205
- const relativePath = path11.relative(this.projectRoot, parsed.path);
8275
+ const relativePath = path13.relative(this.projectRoot, parsed.path);
7206
8276
  stats.parseFailures.push(relativePath);
7207
8277
  }
7208
8278
  let fileChunkCount = 0;
@@ -7223,6 +8293,10 @@ var Indexer = class {
7223
8293
  }
7224
8294
  const id = generateChunkId(parsed.path, chunk);
7225
8295
  const contentHash = generateChunkHash(chunk);
8296
+ const existingContentHash = existingChunks.get(id);
8297
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
8298
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
8299
+ const blameMetadata = metadataFromBlame(blame);
7226
8300
  currentChunkIds.add(id);
7227
8301
  chunkDataBatch.push({
7228
8302
  chunkId: id,
@@ -7232,15 +8306,20 @@ var Indexer = class {
7232
8306
  endLine: chunk.endLine,
7233
8307
  nodeType: chunk.chunkType,
7234
8308
  name: chunk.name,
7235
- language: chunk.language
8309
+ language: chunk.language,
8310
+ blameSha: blameMetadata.blameSha,
8311
+ blameAuthor: blameMetadata.blameAuthor,
8312
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
8313
+ blameCommittedAt: blameMetadata.blameCommittedAt,
8314
+ blameSummary: blameMetadata.blameSummary
7236
8315
  });
7237
- if (existingChunks.get(id) === contentHash) {
8316
+ if (existingContentHash === contentHash) {
7238
8317
  fileChunkCount++;
7239
8318
  continue;
7240
8319
  }
7241
- const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text2) => ({
7242
- text: text2,
7243
- tokenCount: estimateTokens(text2)
8320
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
8321
+ text: text3,
8322
+ tokenCount: estimateTokens(text3)
7244
8323
  }));
7245
8324
  const metadata = {
7246
8325
  filePath: parsed.path,
@@ -7249,7 +8328,8 @@ var Indexer = class {
7249
8328
  chunkType: chunk.chunkType,
7250
8329
  name: chunk.name,
7251
8330
  language: chunk.language,
7252
- hash: contentHash
8331
+ hash: contentHash,
8332
+ ...blameMetadata
7253
8333
  };
7254
8334
  pendingChunks.push({
7255
8335
  id,
@@ -7392,6 +8472,11 @@ var Indexer = class {
7392
8472
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7393
8473
  database.clearBranchSymbols(branchCatalogKey);
7394
8474
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8475
+ const vectorPath = path13.join(this.indexPath, "vectors");
8476
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync8(vectorPath) && existsSync8(`${vectorPath}.meta.json`);
8477
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
8478
+ store.save();
8479
+ }
7395
8480
  if (scopedRoots) {
7396
8481
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7397
8482
  this.clearScopedFailedBatches(scopedRoots);
@@ -7410,7 +8495,6 @@ var Indexer = class {
7410
8495
  chunksProcessed: 0,
7411
8496
  totalChunks: 0
7412
8497
  });
7413
- this.releaseIndexingLock();
7414
8498
  return stats;
7415
8499
  }
7416
8500
  if (pendingChunks.length === 0) {
@@ -7419,7 +8503,7 @@ var Indexer = class {
7419
8503
  database.clearBranchSymbols(branchCatalogKey);
7420
8504
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7421
8505
  store.save();
7422
- invertedIndex.save();
8506
+ this.saveInvertedIndex(invertedIndex);
7423
8507
  if (scopedRoots) {
7424
8508
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7425
8509
  this.clearScopedFailedBatches(scopedRoots);
@@ -7438,7 +8522,6 @@ var Indexer = class {
7438
8522
  chunksProcessed: 0,
7439
8523
  totalChunks: 0
7440
8524
  });
7441
- this.releaseIndexingLock();
7442
8525
  return stats;
7443
8526
  }
7444
8527
  onProgress?.({
@@ -7669,7 +8752,7 @@ var Indexer = class {
7669
8752
  database.clearBranchSymbols(branchCatalogKey);
7670
8753
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7671
8754
  store.save();
7672
- invertedIndex.save();
8755
+ this.saveInvertedIndex(invertedIndex);
7673
8756
  if (scopedRoots) {
7674
8757
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7675
8758
  } else {
@@ -7721,7 +8804,6 @@ var Indexer = class {
7721
8804
  chunksProcessed: stats.indexedChunks,
7722
8805
  totalChunks: pendingChunks.length
7723
8806
  });
7724
- this.releaseIndexingLock();
7725
8807
  return stats;
7726
8808
  }
7727
8809
  async getQueryEmbedding(query, provider) {
@@ -7771,9 +8853,9 @@ var Indexer = class {
7771
8853
  }
7772
8854
  return bestMatch;
7773
8855
  }
7774
- tokenize(text2) {
8856
+ tokenize(text3) {
7775
8857
  return new Set(
7776
- text2.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
8858
+ text3.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
7777
8859
  );
7778
8860
  }
7779
8861
  jaccardSimilarity(a, b) {
@@ -7787,8 +8869,8 @@ var Indexer = class {
7787
8869
  return intersection / union;
7788
8870
  }
7789
8871
  async search(query, limit, options) {
7790
- const { store, provider, database } = await this.ensureInitialized();
7791
- const compatibility = this.checkCompatibility();
8872
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8873
+ this.requireReadableComponents(readIssues, "vectors", "database");
7792
8874
  if (!compatibility.compatible) {
7793
8875
  throw new Error(
7794
8876
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
@@ -7802,6 +8884,7 @@ var Indexer = class {
7802
8884
  const maxResults = limit ?? this.config.search.maxResults;
7803
8885
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
7804
8886
  const fusionStrategy = this.config.search.fusionStrategy;
8887
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
7805
8888
  const rrfK = this.config.search.rrfK;
7806
8889
  const rerankTopN = this.config.search.rerankTopN;
7807
8890
  const filterByBranch = options?.filterByBranch ?? true;
@@ -7810,7 +8893,7 @@ var Indexer = class {
7810
8893
  this.logger.search("debug", "Starting search", {
7811
8894
  query,
7812
8895
  maxResults,
7813
- hybridWeight,
8896
+ hybridWeight: effectiveHybridWeight,
7814
8897
  fusionStrategy,
7815
8898
  rrfK,
7816
8899
  rerankTopN,
@@ -7824,7 +8907,7 @@ var Indexer = class {
7824
8907
  const semanticResults = store.search(embedding, maxResults * 4);
7825
8908
  const vectorMs = performance2.now() - vectorStartTime;
7826
8909
  const keywordStartTime = performance2.now();
7827
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
8910
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
7828
8911
  const keywordMs = performance2.now() - keywordStartTime;
7829
8912
  let branchChunkIds = null;
7830
8913
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -7861,7 +8944,7 @@ var Indexer = class {
7861
8944
  rrfK,
7862
8945
  rerankTopN,
7863
8946
  limit: maxResults,
7864
- hybridWeight,
8947
+ hybridWeight: effectiveHybridWeight,
7865
8948
  prioritizeSourcePaths: sourceIntent
7866
8949
  });
7867
8950
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -7955,13 +9038,13 @@ var Indexer = class {
7955
9038
  content,
7956
9039
  score: r.score,
7957
9040
  chunkType: r.metadata.chunkType,
7958
- name: r.metadata.name
9041
+ name: r.metadata.name,
9042
+ blame: blameFromMetadata(r.metadata)
7959
9043
  };
7960
9044
  })
7961
9045
  );
7962
9046
  }
7963
- async keywordSearch(query, limit) {
7964
- const { store, invertedIndex } = await this.ensureInitialized();
9047
+ async keywordSearch(query, limit, store, invertedIndex) {
7965
9048
  const scores = invertedIndex.search(query);
7966
9049
  if (scores.size === 0) {
7967
9050
  return [];
@@ -7979,24 +9062,54 @@ var Indexer = class {
7979
9062
  return results.slice(0, limit);
7980
9063
  }
7981
9064
  async getStatus() {
7982
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9065
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
7983
9066
  const failedBatchesCount = this.getFailedBatchesCount();
9067
+ const vectorCount = store.count();
9068
+ const statusReadIssues = [...readIssues];
9069
+ let startupWarning = "";
9070
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9071
+ try {
9072
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9073
+ } catch (error) {
9074
+ const message = this.getDatabaseReadIssueMessage();
9075
+ statusReadIssues.push(this.createReadIssue("database", message));
9076
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9077
+ this.recordReadIssue("database", message, error);
9078
+ }
9079
+ }
9080
+ }
9081
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9082
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9083
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
7984
9084
  return {
7985
- indexed: store.count() > 0,
7986
- vectorCount: store.count(),
9085
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9086
+ vectorCount,
7987
9087
  provider: configuredProviderInfo.provider,
7988
9088
  model: configuredProviderInfo.modelInfo.model,
7989
9089
  indexPath: this.indexPath,
7990
9090
  currentBranch: this.currentBranch,
7991
9091
  baseBranch: this.baseBranch,
7992
- compatibility: this.indexCompatibility,
9092
+ compatibility,
7993
9093
  failedBatchesCount,
7994
9094
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
7995
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9095
+ warning: warning || void 0
7996
9096
  };
7997
9097
  }
9098
+ async forceIndex(onProgress) {
9099
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9100
+ await this.ensureInitializedUnlocked(recoveredOwners);
9101
+ await this.clearIndexUnlocked();
9102
+ return this.indexUnlocked(onProgress, [], true);
9103
+ });
9104
+ }
7998
9105
  async clearIndex() {
7999
- const { store, invertedIndex, database } = await this.ensureInitialized();
9106
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9107
+ await this.ensureInitializedUnlocked(recoveredOwners);
9108
+ await this.clearIndexUnlocked();
9109
+ });
9110
+ }
9111
+ async clearIndexUnlocked() {
9112
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8000
9113
  if (this.config.scope === "global") {
8001
9114
  store.load();
8002
9115
  invertedIndex.load();
@@ -8023,7 +9136,7 @@ var Indexer = class {
8023
9136
  store.clear();
8024
9137
  store.save();
8025
9138
  invertedIndex.clear();
8026
- invertedIndex.save();
9139
+ this.saveInvertedIndex(invertedIndex);
8027
9140
  this.fileHashCache.clear();
8028
9141
  this.saveFileHashCache();
8029
9142
  database.clearAllIndexedData();
@@ -8047,14 +9160,7 @@ var Indexer = class {
8047
9160
  this.indexCompatibility = compatibility;
8048
9161
  return;
8049
9162
  }
8050
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8051
- if (this.host !== "opencode") {
8052
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8053
- }
8054
- const isLocalProjectIndex = localProjectIndexPaths.some(
8055
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8056
- );
8057
- if (!isLocalProjectIndex) {
9163
+ if (!this.isLocalProjectIndexPath()) {
8058
9164
  throw new Error(
8059
9165
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8060
9166
  );
@@ -8062,7 +9168,7 @@ var Indexer = class {
8062
9168
  store.clear();
8063
9169
  store.save();
8064
9170
  invertedIndex.clear();
8065
- invertedIndex.save();
9171
+ this.saveInvertedIndex(invertedIndex);
8066
9172
  this.fileHashCache.clear();
8067
9173
  this.saveFileHashCache();
8068
9174
  database.clearAllIndexedData();
@@ -8080,7 +9186,13 @@ var Indexer = class {
8080
9186
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8081
9187
  }
8082
9188
  async healthCheck() {
8083
- const { store, invertedIndex, database } = await this.ensureInitialized();
9189
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9190
+ await this.ensureInitializedUnlocked(recoveredOwners);
9191
+ return this.healthCheckUnlocked();
9192
+ });
9193
+ }
9194
+ async healthCheckUnlocked() {
9195
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8084
9196
  this.logger.gc("info", "Starting health check");
8085
9197
  const allMetadata = store.getAllMetadata();
8086
9198
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8093,7 +9205,7 @@ var Indexer = class {
8093
9205
  const removedChunkKeys = [];
8094
9206
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8095
9207
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8096
- if (!existsSync7(filePath)) {
9208
+ if (!existsSync8(filePath)) {
8097
9209
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8098
9210
  for (const key of chunkKeys) {
8099
9211
  removedChunkKeys.push(key);
@@ -8118,7 +9230,7 @@ var Indexer = class {
8118
9230
  const removedCount = removedChunkKeys.length;
8119
9231
  if (removedCount > 0) {
8120
9232
  store.save();
8121
- invertedIndex.save();
9233
+ this.saveInvertedIndex(invertedIndex);
8122
9234
  }
8123
9235
  let gcOrphanEmbeddings;
8124
9236
  let gcOrphanChunks;
@@ -8133,7 +9245,7 @@ var Indexer = class {
8133
9245
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8134
9246
  throw error;
8135
9247
  }
8136
- await this.ensureInitialized();
9248
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8137
9249
  return {
8138
9250
  removed: 0,
8139
9251
  filePaths: [],
@@ -8142,7 +9254,7 @@ var Indexer = class {
8142
9254
  gcOrphanSymbols: 0,
8143
9255
  gcOrphanCallEdges: 0,
8144
9256
  resetCorruptedIndex: true,
8145
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9257
+ warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
8146
9258
  };
8147
9259
  }
8148
9260
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8155,7 +9267,13 @@ var Indexer = class {
8155
9267
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8156
9268
  }
8157
9269
  async retryFailedBatches() {
8158
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9270
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9271
+ await this.ensureInitializedUnlocked(recoveredOwners);
9272
+ return this.retryFailedBatchesUnlocked();
9273
+ });
9274
+ }
9275
+ async retryFailedBatchesUnlocked() {
9276
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8159
9277
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8160
9278
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8161
9279
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8319,7 +9437,7 @@ var Indexer = class {
8319
9437
  }
8320
9438
  if (succeeded > 0) {
8321
9439
  store.save();
8322
- invertedIndex.save();
9440
+ this.saveInvertedIndex(invertedIndex);
8323
9441
  }
8324
9442
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8325
9443
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8347,15 +9465,16 @@ var Indexer = class {
8347
9465
  }
8348
9466
  }
8349
9467
  async getDatabaseStats() {
8350
- const { database } = await this.ensureInitialized();
9468
+ const { database, readIssues } = await this.ensureInitialized();
9469
+ this.requireReadableComponents(readIssues, "database");
8351
9470
  return database.getStats();
8352
9471
  }
8353
9472
  getLogger() {
8354
9473
  return this.logger;
8355
9474
  }
8356
9475
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8357
- const { store, provider, database } = await this.ensureInitialized();
8358
- const compatibility = this.checkCompatibility();
9476
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9477
+ this.requireReadableComponents(readIssues, "vectors", "database");
8359
9478
  if (!compatibility.compatible) {
8360
9479
  throw new Error(
8361
9480
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8462,13 +9581,15 @@ var Indexer = class {
8462
9581
  content,
8463
9582
  score: r.score,
8464
9583
  chunkType: r.metadata.chunkType,
8465
- name: r.metadata.name
9584
+ name: r.metadata.name,
9585
+ blame: blameFromMetadata(r.metadata)
8466
9586
  };
8467
9587
  })
8468
9588
  );
8469
9589
  }
8470
9590
  async getCallers(targetName, callTypeFilter) {
8471
- const { database } = await this.ensureInitialized();
9591
+ const { database, readIssues } = await this.ensureInitialized();
9592
+ this.requireReadableComponents(readIssues, "database");
8472
9593
  const seen = /* @__PURE__ */ new Set();
8473
9594
  const results = [];
8474
9595
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8482,7 +9603,8 @@ var Indexer = class {
8482
9603
  return results;
8483
9604
  }
8484
9605
  async getCallees(symbolId, callTypeFilter) {
8485
- const { database } = await this.ensureInitialized();
9606
+ const { database, readIssues } = await this.ensureInitialized();
9607
+ this.requireReadableComponents(readIssues, "database");
8486
9608
  const seen = /* @__PURE__ */ new Set();
8487
9609
  const results = [];
8488
9610
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8496,44 +9618,51 @@ var Indexer = class {
8496
9618
  return results;
8497
9619
  }
8498
9620
  async findCallPath(fromName, toName, maxDepth) {
8499
- const { database } = await this.ensureInitialized();
9621
+ const { database, readIssues } = await this.ensureInitialized();
9622
+ this.requireReadableComponents(readIssues, "database");
8500
9623
  let shortest = [];
8501
9624
  for (const branchKey of this.getBranchCatalogKeys()) {
8502
- const path15 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8503
- if (path15.length > 0 && (shortest.length === 0 || path15.length < shortest.length)) {
8504
- shortest = path15;
9625
+ const path17 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9626
+ if (path17.length > 0 && (shortest.length === 0 || path17.length < shortest.length)) {
9627
+ shortest = path17;
8505
9628
  }
8506
9629
  }
8507
9630
  return shortest;
8508
9631
  }
8509
9632
  async getSymbolsForBranch(branch) {
8510
- const { database } = await this.ensureInitialized();
9633
+ const { database, readIssues } = await this.ensureInitialized();
9634
+ this.requireReadableComponents(readIssues, "database");
8511
9635
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8512
9636
  return database.getSymbolsForBranch(resolvedBranch);
8513
9637
  }
8514
9638
  async getSymbolsForFiles(filePaths, branch) {
8515
- const { database } = await this.ensureInitialized();
9639
+ const { database, readIssues } = await this.ensureInitialized();
9640
+ this.requireReadableComponents(readIssues, "database");
8516
9641
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8517
9642
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8518
9643
  }
8519
9644
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8520
- const { database } = await this.ensureInitialized();
9645
+ const { database, readIssues } = await this.ensureInitialized();
9646
+ this.requireReadableComponents(readIssues, "database");
8521
9647
  const branch = this.getBranchCatalogKey();
8522
9648
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8523
9649
  }
8524
9650
  async detectCommunities(branch, symbolIds) {
8525
- const { database } = await this.ensureInitialized();
9651
+ const { database, readIssues } = await this.ensureInitialized();
9652
+ this.requireReadableComponents(readIssues, "database");
8526
9653
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8527
9654
  return database.detectCommunities(resolvedBranch, symbolIds);
8528
9655
  }
8529
9656
  async computeCentrality(branch) {
8530
- const { database } = await this.ensureInitialized();
9657
+ const { database, readIssues } = await this.ensureInitialized();
9658
+ this.requireReadableComponents(readIssues, "database");
8531
9659
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8532
9660
  return database.computeCentrality(resolvedBranch);
8533
9661
  }
8534
9662
  async getPrImpact(opts) {
8535
- const { database } = await this.ensureInitialized();
8536
- const execFileAsync2 = promisify2(execFile2);
9663
+ const { database, readIssues } = await this.ensureInitialized();
9664
+ this.requireReadableComponents(readIssues, "database");
9665
+ const execFileAsync3 = promisify3(execFile3);
8537
9666
  const changedFilesResult = await getChangedFiles({
8538
9667
  pr: opts.pr,
8539
9668
  branch: opts.branch,
@@ -8555,7 +9684,7 @@ var Indexer = class {
8555
9684
  "Run index_codebase first to build the call graph and symbol index for this branch."
8556
9685
  );
8557
9686
  }
8558
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9687
+ const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
8559
9688
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8560
9689
  const directIds = directSymbols.map((s) => s.id);
8561
9690
  const direction = opts.direction ?? "both";
@@ -8617,7 +9746,7 @@ var Indexer = class {
8617
9746
  if (opts.checkConflicts) {
8618
9747
  conflictingPRs = [];
8619
9748
  try {
8620
- const { stdout } = await execFileAsync2(
9749
+ const { stdout } = await execFileAsync3(
8621
9750
  "gh",
8622
9751
  ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8623
9752
  { cwd: this.projectRoot, timeout: 3e4 }
@@ -8638,7 +9767,7 @@ var Indexer = class {
8638
9767
  projectRoot: this.projectRoot,
8639
9768
  baseBranch: this.baseBranch
8640
9769
  });
8641
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9770
+ const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
8642
9771
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8643
9772
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8644
9773
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8688,7 +9817,8 @@ var Indexer = class {
8688
9817
  };
8689
9818
  }
8690
9819
  async getVisualizationData(options) {
8691
- const { database, store } = await this.ensureInitialized();
9820
+ const { database, store, readIssues } = await this.ensureInitialized();
9821
+ this.requireReadableComponents(readIssues, "vectors", "database");
8692
9822
  const seenSymbols = /* @__PURE__ */ new Map();
8693
9823
  const seenEdges = /* @__PURE__ */ new Map();
8694
9824
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8701,12 +9831,12 @@ var Indexer = class {
8701
9831
  if (meta.filePath) filePaths.add(meta.filePath);
8702
9832
  }
8703
9833
  const directory = options?.directory?.replace(/\/$/, "");
8704
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9834
+ const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
8705
9835
  for (const filePath of filePaths) {
8706
9836
  if (directory) {
8707
- const absoluteFilePath = path11.resolve(filePath);
9837
+ const absoluteFilePath = path13.resolve(filePath);
8708
9838
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8709
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9839
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
8710
9840
  if (!matchesRelative && !matchesProjectRelative) {
8711
9841
  continue;
8712
9842
  }
@@ -8728,53 +9858,64 @@ var Indexer = class {
8728
9858
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
8729
9859
  }
8730
9860
  async close() {
8731
- await this.database?.close();
9861
+ this.database?.close();
9862
+ for (const database of this.retiredDatabases) {
9863
+ database.close();
9864
+ }
9865
+ this.retiredDatabases = [];
8732
9866
  this.database = null;
8733
9867
  this.store = null;
8734
9868
  this.invertedIndex = null;
8735
9869
  this.provider = null;
8736
9870
  this.reranker = null;
9871
+ this.configuredProviderInfo = null;
9872
+ this.indexCompatibility = null;
9873
+ this.initializationMode = "none";
9874
+ this.readIssues = [];
9875
+ this.readerArtifactFingerprint = null;
9876
+ this.writerArtifactFingerprint = null;
9877
+ this.readerArtifactRetryAfter.clear();
8737
9878
  }
8738
9879
  };
8739
9880
 
8740
9881
  // src/tools/knowledge-base-paths.ts
8741
- import * as path12 from "path";
9882
+ import * as path14 from "path";
8742
9883
  function resolveConfigPathValue(value, baseDir) {
8743
9884
  const trimmed = value.trim();
8744
9885
  if (!trimmed) {
8745
9886
  return trimmed;
8746
9887
  }
8747
- const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
8748
- return path12.normalize(absolutePath);
9888
+ const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9889
+ return path14.normalize(absolutePath);
8749
9890
  }
8750
9891
  function serializeConfigPathValue(value, baseDir) {
8751
9892
  const trimmed = value.trim();
8752
9893
  if (!trimmed) {
8753
9894
  return trimmed;
8754
9895
  }
8755
- if (!path12.isAbsolute(trimmed)) {
8756
- return normalizePathSeparators(path12.normalize(trimmed));
9896
+ if (!path14.isAbsolute(trimmed)) {
9897
+ return normalizePathSeparators(path14.normalize(trimmed));
8757
9898
  }
8758
- const relativePath = path12.relative(baseDir, trimmed);
8759
- if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
8760
- return normalizePathSeparators(path12.normalize(relativePath || "."));
9899
+ const relativePath = path14.relative(baseDir, trimmed);
9900
+ if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9901
+ return normalizePathSeparators(path14.normalize(relativePath || "."));
8761
9902
  }
8762
- return path12.normalize(trimmed);
9903
+ return path14.normalize(trimmed);
8763
9904
  }
8764
- function resolveKnowledgeBasePath(value, projectRoot2) {
8765
- return path12.isAbsolute(value) ? value : path12.resolve(projectRoot2, value);
9905
+ function resolveKnowledgeBasePath(value, projectRoot3) {
9906
+ return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
8766
9907
  }
8767
- function normalizeKnowledgeBasePath2(value, projectRoot2) {
8768
- return path12.normalize(resolveKnowledgeBasePath(value, projectRoot2));
9908
+ function normalizeKnowledgeBasePath2(value, projectRoot3) {
9909
+ return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
8769
9910
  }
8770
- function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot2) {
8771
- const normalizedInput = path12.normalize(inputPath);
8772
- return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput);
9911
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
9912
+ const normalizedInput = path14.normalize(inputPath);
9913
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
8773
9914
  }
8774
- function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot2) {
8775
- const normalizedInput = path12.normalize(inputPath);
9915
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
9916
+ const normalizedInput = path14.normalize(inputPath);
8776
9917
  return knowledgeBases.findIndex(
8777
- (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput
9918
+ (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
8778
9919
  );
8779
9920
  }
8780
9921
 
@@ -8874,6 +10015,10 @@ function formatStatus(status) {
8874
10015
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
8875
10016
  }
8876
10017
  }
10018
+ if (status.warning) {
10019
+ lines.push("");
10020
+ lines.push(`INDEX WARNING: ${status.warning}`);
10021
+ }
8877
10022
  if (status.compatibility && !status.compatibility.compatible) {
8878
10023
  lines.push("");
8879
10024
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -8947,16 +10092,57 @@ function formatHealthCheck(result) {
8947
10092
  }
8948
10093
  return lines.join("\n");
8949
10094
  }
10095
+ function formatCallGraphCallers(name, callers, relationshipType) {
10096
+ if (callers.length === 0) {
10097
+ return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
10098
+ }
10099
+ const formatted = callers.map((edge, index) => {
10100
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10101
+ 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]"}`;
10102
+ });
10103
+ return `"${name}" is called by ${callers.length} function(s):
10104
+
10105
+ ${formatted.join("\n")}`;
10106
+ }
10107
+ function formatCallGraphCallees(symbolId, callees, relationshipType) {
10108
+ if (callees.length === 0) {
10109
+ return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
10110
+ }
10111
+ return callees.map((edge, index) => {
10112
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
10113
+ return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
10114
+ }).join("\n");
10115
+ }
10116
+ function formatCallGraphPath(from, to, path17) {
10117
+ if (path17.length === 0) {
10118
+ return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
10119
+ }
10120
+ const formatted = path17.map((hop, index) => {
10121
+ const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
10122
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10123
+ return `${prefix} ${hop.symbolName}${location}`;
10124
+ });
10125
+ return `Path (${path17.length} hops):
10126
+ ${formatted.join("\n")}`;
10127
+ }
8950
10128
  function formatResultHeader(result, index) {
8951
10129
  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}`;
8952
10130
  }
10131
+ function formatBlame(result) {
10132
+ if (!result.blame) {
10133
+ return "";
10134
+ }
10135
+ const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
10136
+ return `
10137
+ ${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
10138
+ }
8953
10139
  function formatDefinitionLookup(results, query) {
8954
10140
  if (results.length === 0) {
8955
10141
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
8956
10142
  }
8957
10143
  const formatted = results.map((r, idx) => {
8958
10144
  const header = formatResultHeader(r, idx);
8959
- return `${header} (score: ${r.score.toFixed(2)})
10145
+ return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
8960
10146
  \`\`\`
8961
10147
  ${truncateContent(r.content)}
8962
10148
  \`\`\``;
@@ -8967,7 +10153,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
8967
10153
  const formatted = results.map((r, idx) => {
8968
10154
  const header = formatResultHeader(r, idx);
8969
10155
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
8970
- return `${header} ${scoreLabel}
10156
+ return `${header} ${scoreLabel}${formatBlame(r)}
8971
10157
  \`\`\`
8972
10158
  ${truncateContent(r.content)}
8973
10159
  \`\`\``;
@@ -8976,13 +10162,13 @@ ${truncateContent(r.content)}
8976
10162
  }
8977
10163
 
8978
10164
  // src/tools/config-state.ts
8979
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
8980
- import * as path13 from "path";
8981
- function normalizeKnowledgeBasePaths(config, projectRoot2) {
10165
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10166
+ import * as path15 from "path";
10167
+ function normalizeKnowledgeBasePaths(config, projectRoot3) {
8982
10168
  const normalized = { ...config };
8983
10169
  if (Array.isArray(normalized.knowledgeBases)) {
8984
10170
  normalized.knowledgeBases = normalized.knowledgeBases.map(
8985
- (kb) => resolveConfigPathValue(kb, projectRoot2)
10171
+ (kb) => resolveConfigPathValue(kb, projectRoot3)
8986
10172
  );
8987
10173
  }
8988
10174
  return normalized;
@@ -8993,21 +10179,21 @@ function toConfigRecord(rawConfig) {
8993
10179
  }
8994
10180
  return { ...rawConfig };
8995
10181
  }
8996
- function getConfigPath(projectRoot2, host = "opencode") {
8997
- return resolveWritableProjectConfigPath(projectRoot2, host);
10182
+ function getConfigPath(projectRoot3, host = "opencode") {
10183
+ return resolveWritableProjectConfigPath(projectRoot3, host);
8998
10184
  }
8999
- function loadRuntimeConfig(projectRoot2, host = "opencode") {
9000
- return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot2, host)), projectRoot2);
10185
+ function loadRuntimeConfig(projectRoot3, host = "opencode") {
10186
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot3, host)), projectRoot3);
9001
10187
  }
9002
- function loadEditableConfig(projectRoot2, host = "opencode") {
9003
- return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot2, host)), projectRoot2);
10188
+ function loadEditableConfig(projectRoot3, host = "opencode") {
10189
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot3, host)), projectRoot3);
9004
10190
  }
9005
- function saveConfig(projectRoot2, config, host = "opencode") {
9006
- const configPath = getConfigPath(projectRoot2, host);
9007
- const configDir = path13.dirname(configPath);
9008
- const configBaseDir = path13.dirname(configDir);
9009
- if (!existsSync8(configDir)) {
9010
- mkdirSync3(configDir, { recursive: true });
10191
+ function saveConfig(projectRoot3, config, host = "opencode") {
10192
+ const configPath = getConfigPath(projectRoot3, host);
10193
+ const configDir = path15.dirname(configPath);
10194
+ const configBaseDir = path15.dirname(configDir);
10195
+ if (!existsSync9(configDir)) {
10196
+ mkdirSync4(configDir, { recursive: true });
9011
10197
  }
9012
10198
  const serializableConfig = { ...config };
9013
10199
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -9015,16 +10201,34 @@ function saveConfig(projectRoot2, config, host = "opencode") {
9015
10201
  (kb) => serializeConfigPathValue(kb, configBaseDir)
9016
10202
  );
9017
10203
  }
9018
- writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10204
+ writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
9019
10205
  }
9020
10206
 
9021
10207
  // src/tools/operations.ts
9022
10208
  var indexerCache = /* @__PURE__ */ new Map();
9023
10209
  var configCache = /* @__PURE__ */ new Map();
9024
10210
  var defaultProjectRoots = /* @__PURE__ */ new Map();
9025
- function getProjectRoot(projectRoot2, host) {
9026
- if (projectRoot2) {
9027
- return projectRoot2;
10211
+ function getIndexBusyResult(error) {
10212
+ if (!isIndexLockContentionError(error)) return null;
10213
+ const owner = error.owner;
10214
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
10215
+ if (error.reason === "legacy-lock") {
10216
+ return {
10217
+ kind: "busy",
10218
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
10219
+ };
10220
+ }
10221
+ if (error.reason === "unknown-owner") {
10222
+ return {
10223
+ kind: "busy",
10224
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
10225
+ };
10226
+ }
10227
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
10228
+ }
10229
+ function getProjectRoot(projectRoot3, host) {
10230
+ if (projectRoot3) {
10231
+ return projectRoot3;
9028
10232
  }
9029
10233
  const root = defaultProjectRoots.get(host);
9030
10234
  if (!root) {
@@ -9032,53 +10236,56 @@ function getProjectRoot(projectRoot2, host) {
9032
10236
  }
9033
10237
  return root;
9034
10238
  }
9035
- function getIndexerCacheKey(projectRoot2, host) {
9036
- return `${host}::${projectRoot2}`;
10239
+ function getIndexerCacheKey(projectRoot3, host) {
10240
+ return `${host}::${projectRoot3}`;
9037
10241
  }
9038
- function getOrCreateIndexer(projectRoot2, host) {
9039
- const key = getIndexerCacheKey(projectRoot2, host);
10242
+ function getOrCreateIndexer(projectRoot3, host) {
10243
+ const key = getIndexerCacheKey(projectRoot3, host);
9040
10244
  const cached = indexerCache.get(key);
9041
10245
  if (cached) {
9042
10246
  return cached;
9043
10247
  }
9044
- const config = parseConfig(loadRuntimeConfig(projectRoot2, host));
9045
- const indexer = new Indexer(projectRoot2, config, host);
10248
+ const config = parseConfig(loadRuntimeConfig(projectRoot3, host));
10249
+ const indexer = new Indexer(projectRoot3, config, host);
9046
10250
  indexerCache.set(key, indexer);
9047
10251
  configCache.set(key, config);
9048
10252
  return indexer;
9049
10253
  }
9050
- function getIndexerForProject(projectRoot2, host = "opencode") {
9051
- const root = getProjectRoot(projectRoot2, host);
10254
+ function getIndexerForProject(projectRoot3, host = "opencode") {
10255
+ const root = getProjectRoot(projectRoot3, host);
9052
10256
  return getOrCreateIndexer(root, host);
9053
10257
  }
9054
- function refreshIndexerForDirectory(projectRoot2, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot2, host))) {
9055
- const key = getIndexerCacheKey(projectRoot2, host);
9056
- const indexer = new Indexer(projectRoot2, config, host);
10258
+ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot3, host))) {
10259
+ const key = getIndexerCacheKey(projectRoot3, host);
10260
+ const indexer = new Indexer(projectRoot3, config, host);
9057
10261
  indexerCache.set(key, indexer);
9058
10262
  configCache.set(key, config);
9059
10263
  }
9060
- function shouldForceLocalizeProjectIndex(projectRoot2, host = "opencode") {
9061
- const root = getProjectRoot(projectRoot2, host);
9062
- const localIndexPath = path14.join(root, getHostProjectIndexRelativePath(host));
9063
- if (existsSync9(localIndexPath)) {
10264
+ function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
10265
+ const root = getProjectRoot(projectRoot3, host);
10266
+ const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10267
+ if (existsSync10(localIndexPath)) {
9064
10268
  return false;
9065
10269
  }
9066
10270
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
9067
10271
  return inheritedIndexPath !== null;
9068
10272
  }
9069
- async function searchCodebase(projectRoot2, host, query, options = {}) {
9070
- const indexer = getIndexerForProject(projectRoot2, host);
10273
+ async function searchCodebase(projectRoot3, host, query, options = {}) {
10274
+ const indexer = getIndexerForProject(projectRoot3, host);
9071
10275
  return indexer.search(query, options.limit, {
9072
10276
  fileType: options.fileType,
9073
10277
  directory: options.directory,
9074
10278
  chunkType: options.chunkType,
9075
10279
  contextLines: options.contextLines,
9076
10280
  metadataOnly: options.metadataOnly,
9077
- definitionIntent: options.definitionIntent
10281
+ definitionIntent: options.definitionIntent,
10282
+ blameAuthor: options.blameAuthor,
10283
+ blameSha: options.blameSha,
10284
+ blameSince: options.blameSince
9078
10285
  });
9079
10286
  }
9080
- async function findSimilarCode(projectRoot2, host, code, options = {}) {
9081
- const indexer = getIndexerForProject(projectRoot2, host);
10287
+ async function findSimilarCode(projectRoot3, host, code, options = {}) {
10288
+ const indexer = getIndexerForProject(projectRoot3, host);
9082
10289
  return indexer.findSimilar(code, options.limit, {
9083
10290
  fileType: options.fileType,
9084
10291
  directory: options.directory,
@@ -9086,16 +10293,16 @@ async function findSimilarCode(projectRoot2, host, code, options = {}) {
9086
10293
  excludeFile: options.excludeFile
9087
10294
  });
9088
10295
  }
9089
- async function implementationLookup(projectRoot2, host, query, options = {}) {
9090
- const indexer = getIndexerForProject(projectRoot2, host);
10296
+ async function implementationLookup(projectRoot3, host, query, options = {}) {
10297
+ const indexer = getIndexerForProject(projectRoot3, host);
9091
10298
  return indexer.search(query, options.limit, {
9092
10299
  fileType: options.fileType,
9093
10300
  directory: options.directory,
9094
10301
  definitionIntent: true
9095
10302
  });
9096
10303
  }
9097
- async function getCallGraphData(projectRoot2, host, params) {
9098
- const indexer = getIndexerForProject(projectRoot2, host);
10304
+ async function getCallGraphData(projectRoot3, host, params) {
10305
+ const indexer = getIndexerForProject(projectRoot3, host);
9099
10306
  if (params.direction === "callees") {
9100
10307
  if (!params.symbolId) {
9101
10308
  return { direction: "callees", callees: [], callers: [] };
@@ -9106,53 +10313,73 @@ async function getCallGraphData(projectRoot2, host, params) {
9106
10313
  const callers = await indexer.getCallers(params.name, params.relationshipType);
9107
10314
  return { direction: "callers", callers, callees: [] };
9108
10315
  }
9109
- async function getCallGraphPath(projectRoot2, host, from, to, maxDepth) {
9110
- const indexer = getIndexerForProject(projectRoot2, host);
10316
+ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
10317
+ const indexer = getIndexerForProject(projectRoot3, host);
9111
10318
  return indexer.findCallPath(from, to, maxDepth);
9112
10319
  }
9113
- async function runIndexCodebase(projectRoot2, host, args, onProgress) {
9114
- const root = getProjectRoot(projectRoot2, host);
10320
+ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10321
+ const root = getProjectRoot(projectRoot3, host);
9115
10322
  const key = getIndexerCacheKey(root, host);
9116
- const cachedConfig = configCache.get(key);
9117
10323
  let indexer = getIndexerForProject(root, host);
9118
- if (args.estimateOnly) {
9119
- return { kind: "estimate", estimate: await indexer.estimateCost() };
9120
- }
9121
- if (args.force) {
9122
- if (shouldForceLocalizeProjectIndex(root, host)) {
9123
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
9124
- refreshIndexerForDirectory(root, host, cachedConfig);
9125
- indexer = getIndexerForProject(root, host);
9126
- }
9127
- await indexer.clearIndex();
9128
- refreshIndexerForDirectory(root, host, cachedConfig);
9129
- indexer = getIndexerForProject(root, host);
9130
- }
9131
- const stats = await indexer.index((progress) => {
9132
- if (onProgress) {
9133
- return onProgress(formatProgressTitle(progress), {
9134
- phase: progress.phase,
9135
- filesProcessed: progress.filesProcessed,
9136
- totalFiles: progress.totalFiles,
9137
- chunksProcessed: progress.chunksProcessed,
9138
- totalChunks: progress.totalChunks,
9139
- percentage: calculatePercentage(progress)
10324
+ const runtimeConfig = configCache.get(key);
10325
+ try {
10326
+ if (args.estimateOnly) {
10327
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
10328
+ }
10329
+ const runIndex = async (target) => {
10330
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
10331
+ return operation((progress) => {
10332
+ if (onProgress) {
10333
+ return onProgress(formatProgressTitle(progress), {
10334
+ phase: progress.phase,
10335
+ filesProcessed: progress.filesProcessed,
10336
+ totalFiles: progress.totalFiles,
10337
+ chunksProcessed: progress.chunksProcessed,
10338
+ totalChunks: progress.totalChunks,
10339
+ percentage: calculatePercentage(progress)
10340
+ });
10341
+ }
10342
+ return Promise.resolve();
9140
10343
  });
10344
+ };
10345
+ let stats;
10346
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10347
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10348
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10349
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10350
+ refreshIndexerForDirectory(root, host, runtimeConfig);
10351
+ indexer = getIndexerForProject(root, host);
10352
+ return runIndex(indexer);
10353
+ }, { completeRecoveries: false });
10354
+ } else {
10355
+ stats = await runIndex(indexer);
9141
10356
  }
9142
- return Promise.resolve();
9143
- });
9144
- return { kind: "stats", stats };
10357
+ return { kind: "stats", stats };
10358
+ } catch (error) {
10359
+ const busyResult = getIndexBusyResult(error);
10360
+ if (!busyResult) throw error;
10361
+ return busyResult;
10362
+ }
9145
10363
  }
9146
- async function getIndexStatus(projectRoot2, host) {
9147
- const indexer = getIndexerForProject(projectRoot2, host);
10364
+ async function getIndexStatus(projectRoot3, host) {
10365
+ const indexer = getIndexerForProject(projectRoot3, host);
9148
10366
  return indexer.getStatus();
9149
10367
  }
9150
- async function getIndexHealthCheck(projectRoot2, host) {
9151
- const indexer = getIndexerForProject(projectRoot2, host);
10368
+ async function getIndexHealthCheck(projectRoot3, host) {
10369
+ const indexer = getIndexerForProject(projectRoot3, host);
9152
10370
  return indexer.healthCheck();
9153
10371
  }
9154
- async function getPrImpact(projectRoot2, host, params) {
9155
- const indexer = getIndexerForProject(projectRoot2, host);
10372
+ async function runIndexHealthCheck(projectRoot3, host) {
10373
+ try {
10374
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot3, host) };
10375
+ } catch (error) {
10376
+ const busyResult = getIndexBusyResult(error);
10377
+ if (!busyResult) throw error;
10378
+ return busyResult;
10379
+ }
10380
+ }
10381
+ async function getPrImpact(projectRoot3, host, params) {
10382
+ const indexer = getIndexerForProject(projectRoot3, host);
9156
10383
  return indexer.getPrImpact({
9157
10384
  pr: params.pr,
9158
10385
  branch: params.branch,
@@ -9162,8 +10389,8 @@ async function getPrImpact(projectRoot2, host, params) {
9162
10389
  direction: params.direction
9163
10390
  });
9164
10391
  }
9165
- async function getIndexMetrics(projectRoot2, host) {
9166
- const indexer = getIndexerForProject(projectRoot2, host);
10392
+ async function getIndexMetrics(projectRoot3, host) {
10393
+ const indexer = getIndexerForProject(projectRoot3, host);
9167
10394
  const logger = indexer.getLogger();
9168
10395
  if (!logger.isEnabled()) {
9169
10396
  return {
@@ -9185,8 +10412,8 @@ async function getIndexMetrics(projectRoot2, host) {
9185
10412
  text: logger.formatMetrics()
9186
10413
  };
9187
10414
  }
9188
- async function getIndexLogs(projectRoot2, host, args) {
9189
- const indexer = getIndexerForProject(projectRoot2, host);
10415
+ async function getIndexLogs(projectRoot3, host, args) {
10416
+ const indexer = getIndexerForProject(projectRoot3, host);
9190
10417
  const logger = indexer.getLogger();
9191
10418
  if (!logger.isEnabled()) {
9192
10419
  return {
@@ -9208,24 +10435,24 @@ async function getIndexLogs(projectRoot2, host, args) {
9208
10435
  text: "No logs recorded yet. Logs are captured during indexing and search operations."
9209
10436
  };
9210
10437
  }
9211
- const text2 = logs.map((entry) => {
10438
+ const text3 = logs.map((entry) => {
9212
10439
  const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
9213
10440
  return `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.category}] ${entry.message}${dataStr}`;
9214
10441
  }).join("\n");
9215
- return { kind: "entries", text: text2 };
10442
+ return { kind: "entries", text: text3 };
9216
10443
  }
9217
- function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9218
- const root = getProjectRoot(projectRoot2, host);
10444
+ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
10445
+ const root = getProjectRoot(projectRoot3, host);
9219
10446
  const inputPath = knowledgeBasePath.trim();
9220
- const normalizedPath = path14.resolve(
9221
- path14.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
10447
+ const normalizedPath = path16.resolve(
10448
+ path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9222
10449
  );
9223
- if (!existsSync9(normalizedPath)) {
10450
+ if (!existsSync10(normalizedPath)) {
9224
10451
  return `Error: Directory does not exist: ${normalizedPath}`;
9225
10452
  }
9226
10453
  let realPath;
9227
10454
  try {
9228
- realPath = realpathSync(normalizedPath);
10455
+ realPath = realpathSync2(normalizedPath);
9229
10456
  } catch {
9230
10457
  return `Error: Cannot resolve path: ${normalizedPath}`;
9231
10458
  }
@@ -9254,13 +10481,13 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9254
10481
  }
9255
10482
  }
9256
10483
  for (const dotDir of sensitiveDotDirs) {
9257
- const sensitiveDir = path14.join(homeDir, dotDir);
10484
+ const sensitiveDir = path16.join(homeDir, dotDir);
9258
10485
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9259
10486
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9260
10487
  }
9261
10488
  }
9262
10489
  try {
9263
- const stat = statSync3(normalizedPath);
10490
+ const stat = statSync4(normalizedPath);
9264
10491
  if (!stat.isDirectory()) {
9265
10492
  return `Error: Path is not a directory: ${normalizedPath}`;
9266
10493
  }
@@ -9287,8 +10514,8 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9287
10514
  Run /index to rebuild the index with the new knowledge base.`;
9288
10515
  return result;
9289
10516
  }
9290
- function listKnowledgeBases(projectRoot2, host) {
9291
- const root = getProjectRoot(projectRoot2, host);
10517
+ function listKnowledgeBases(projectRoot3, host) {
10518
+ const root = getProjectRoot(projectRoot3, host);
9292
10519
  const config = loadRuntimeConfig(root, host);
9293
10520
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9294
10521
  if (knowledgeBases.length === 0) {
@@ -9300,7 +10527,7 @@ function listKnowledgeBases(projectRoot2, host) {
9300
10527
  for (let i = 0; i < knowledgeBases.length; i++) {
9301
10528
  const kb = knowledgeBases[i];
9302
10529
  const resolvedPath = resolveKnowledgeBasePath(kb, root);
9303
- const exists = existsSync9(resolvedPath);
10530
+ const exists = existsSync10(resolvedPath);
9304
10531
  result += `[${i + 1}] ${kb}
9305
10532
  `;
9306
10533
  result += ` Resolved: ${resolvedPath}
@@ -9309,7 +10536,7 @@ function listKnowledgeBases(projectRoot2, host) {
9309
10536
  `;
9310
10537
  if (exists) {
9311
10538
  try {
9312
- const stat = statSync3(resolvedPath);
10539
+ const stat = statSync4(resolvedPath);
9313
10540
  result += ` Type: ${stat.isDirectory() ? "Directory" : "File"}
9314
10541
  `;
9315
10542
  } catch {
@@ -9317,7 +10544,7 @@ function listKnowledgeBases(projectRoot2, host) {
9317
10544
  }
9318
10545
  result += "\n";
9319
10546
  }
9320
- const hasHostConfig = existsSync9(path14.join(root, getHostProjectConfigRelativePath(host)));
10547
+ const hasHostConfig = existsSync10(path16.join(root, getHostProjectConfigRelativePath(host)));
9321
10548
  if (hasHostConfig) {
9322
10549
  result += `
9323
10550
  Config sources: 1 file(s).`;
@@ -9326,8 +10553,8 @@ Config sources: 1 file(s).`;
9326
10553
  Config file: ${getConfigPath(root, host)}`;
9327
10554
  return result;
9328
10555
  }
9329
- function removeKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9330
- const root = getProjectRoot(projectRoot2, host);
10556
+ function removeKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
10557
+ const root = getProjectRoot(projectRoot3, host);
9331
10558
  const config = loadEditableConfig(root, host);
9332
10559
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9333
10560
  const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
@@ -9350,17 +10577,9 @@ Run /index to rebuild the index without the removed knowledge base.`;
9350
10577
  return result;
9351
10578
  }
9352
10579
 
9353
- // src/pi-extension.ts
10580
+ // src/pi-call-graph.ts
10581
+ import { Type } from "typebox";
9354
10582
  var HOST = "pi";
9355
- var ChunkType = Type.Union([
9356
- Type.Literal("function"),
9357
- Type.Literal("class"),
9358
- Type.Literal("method"),
9359
- Type.Literal("interface"),
9360
- Type.Literal("type"),
9361
- Type.Literal("module"),
9362
- Type.Literal("block")
9363
- ]);
9364
10583
  var RelationshipType = Type.Union([
9365
10584
  Type.Literal("Call"),
9366
10585
  Type.Literal("MethodCall"),
@@ -9369,214 +10588,250 @@ var RelationshipType = Type.Union([
9369
10588
  Type.Literal("Inherits"),
9370
10589
  Type.Literal("Implements")
9371
10590
  ]);
9372
- function text(text2, details) {
9373
- return { content: [{ type: "text", text: text2 }], details };
10591
+ function text(text3, details) {
10592
+ return { content: [{ type: "text", text: text3 }], details };
9374
10593
  }
9375
10594
  function projectRoot(ctx) {
9376
10595
  return ctx?.cwd ?? process.cwd();
9377
10596
  }
10597
+ function registerPiCallGraphTools(pi) {
10598
+ pi.registerTool({
10599
+ name: "call_graph",
10600
+ label: "Call Graph",
10601
+ description: "Find callers or callees of a function/method in the indexed call graph.",
10602
+ parameters: Type.Object({
10603
+ name: Type.String(),
10604
+ direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees")])),
10605
+ symbolId: Type.Optional(Type.String()),
10606
+ relationshipType: Type.Optional(RelationshipType)
10607
+ }),
10608
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
10609
+ const result = await getCallGraphData(projectRoot(ctx), HOST, params);
10610
+ if (result.direction === "callees") {
10611
+ return text(
10612
+ params.symbolId ? formatCallGraphCallees(params.symbolId, result.callees, params.relationshipType) : "Error: 'symbolId' is required when direction is 'callees'.",
10613
+ result
10614
+ );
10615
+ }
10616
+ return text(formatCallGraphCallers(params.name, result.callers, params.relationshipType), result);
10617
+ }
10618
+ });
10619
+ pi.registerTool({
10620
+ name: "call_graph_path",
10621
+ label: "Call Graph Path",
10622
+ description: "Find a call path between two functions/methods.",
10623
+ parameters: Type.Object({
10624
+ from: Type.String(),
10625
+ to: Type.String(),
10626
+ maxDepth: Type.Optional(Type.Number({ default: 10 }))
10627
+ }),
10628
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
10629
+ const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
10630
+ return text(formatCallGraphPath(params.from, params.to, result), result);
10631
+ }
10632
+ });
10633
+ }
10634
+
10635
+ // src/pi-extension.ts
10636
+ var HOST2 = "pi";
10637
+ var ChunkType = Type2.Union([
10638
+ Type2.Literal("function"),
10639
+ Type2.Literal("class"),
10640
+ Type2.Literal("method"),
10641
+ Type2.Literal("interface"),
10642
+ Type2.Literal("type"),
10643
+ Type2.Literal("module"),
10644
+ Type2.Literal("block")
10645
+ ]);
10646
+ function text2(text3, details) {
10647
+ return { content: [{ type: "text", text: text3 }], details };
10648
+ }
10649
+ function projectRoot2(ctx) {
10650
+ return ctx?.cwd ?? process.cwd();
10651
+ }
9378
10652
  function codebaseIndexPiExtension(pi) {
9379
10653
  pi.registerTool({
9380
10654
  name: "codebase_search",
9381
10655
  label: "Codebase Search",
9382
10656
  description: "Semantic search over the indexed codebase. Describe behavior, not syntax.",
9383
- parameters: Type.Object({
9384
- query: Type.String({ description: "Natural language description of what code you're looking for" }),
9385
- limit: Type.Optional(Type.Number({ description: "Maximum results (default: 10)" })),
9386
- fileType: Type.Optional(Type.String({ description: "Filter by extension, e.g. ts, py, rs" })),
9387
- directory: Type.Optional(Type.String({ description: "Filter by directory path" })),
9388
- chunkType: Type.Optional(ChunkType),
9389
- contextLines: Type.Optional(Type.Number({ description: "Extra lines around each match" }))
10657
+ parameters: Type2.Object({
10658
+ query: Type2.String({ description: "Natural language description of what code you're looking for" }),
10659
+ limit: Type2.Optional(Type2.Number({ description: "Maximum results (default: 10)" })),
10660
+ fileType: Type2.Optional(Type2.String({ description: "Filter by extension, e.g. ts, py, rs" })),
10661
+ directory: Type2.Optional(Type2.String({ description: "Filter by directory path" })),
10662
+ chunkType: Type2.Optional(ChunkType),
10663
+ contextLines: Type2.Optional(Type2.Number({ description: "Extra lines around each match" })),
10664
+ blameAuthor: Type2.Optional(Type2.String({ description: "Filter by git blame author name or email" })),
10665
+ blameSha: Type2.Optional(Type2.String({ description: "Filter by git blame commit SHA or prefix" })),
10666
+ blameSince: Type2.Optional(Type2.String({ description: "Filter to chunks last changed on or after this date" }))
9390
10667
  }),
9391
10668
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9392
- const results = await searchCodebase(projectRoot(ctx), HOST, params.query, params);
9393
- return text(formatSearchResults(results), results);
10669
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, params);
10670
+ return text2(formatSearchResults(results), results);
9394
10671
  }
9395
10672
  });
9396
10673
  pi.registerTool({
9397
10674
  name: "codebase_peek",
9398
10675
  label: "Codebase Peek",
9399
10676
  description: "Semantic search returning only metadata (file, lines, symbol) to save tokens.",
9400
- parameters: Type.Object({
9401
- query: Type.String(),
9402
- limit: Type.Optional(Type.Number()),
9403
- fileType: Type.Optional(Type.String()),
9404
- directory: Type.Optional(Type.String()),
9405
- chunkType: Type.Optional(ChunkType)
10677
+ parameters: Type2.Object({
10678
+ query: Type2.String(),
10679
+ limit: Type2.Optional(Type2.Number()),
10680
+ fileType: Type2.Optional(Type2.String()),
10681
+ directory: Type2.Optional(Type2.String()),
10682
+ chunkType: Type2.Optional(ChunkType),
10683
+ blameAuthor: Type2.Optional(Type2.String()),
10684
+ blameSha: Type2.Optional(Type2.String()),
10685
+ blameSince: Type2.Optional(Type2.String())
9406
10686
  }),
9407
10687
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9408
- const results = await searchCodebase(projectRoot(ctx), HOST, params.query, { ...params, metadataOnly: true });
9409
- return text(formatSearchResults(results), results);
10688
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, { ...params, metadataOnly: true });
10689
+ return text2(formatSearchResults(results), results);
9410
10690
  }
9411
10691
  });
9412
10692
  pi.registerTool({
9413
10693
  name: "find_similar",
9414
10694
  label: "Find Similar Code",
9415
10695
  description: "Find code similar to a snippet for duplicate detection and pattern discovery.",
9416
- parameters: Type.Object({
9417
- code: Type.String({ description: "Code snippet to compare" }),
9418
- limit: Type.Optional(Type.Number()),
9419
- fileType: Type.Optional(Type.String()),
9420
- directory: Type.Optional(Type.String()),
9421
- chunkType: Type.Optional(ChunkType),
9422
- excludeFile: Type.Optional(Type.String())
10696
+ parameters: Type2.Object({
10697
+ code: Type2.String({ description: "Code snippet to compare" }),
10698
+ limit: Type2.Optional(Type2.Number()),
10699
+ fileType: Type2.Optional(Type2.String()),
10700
+ directory: Type2.Optional(Type2.String()),
10701
+ chunkType: Type2.Optional(ChunkType),
10702
+ excludeFile: Type2.Optional(Type2.String())
9423
10703
  }),
9424
10704
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9425
- const results = await findSimilarCode(projectRoot(ctx), HOST, params.code, params);
9426
- return text(formatSearchResults(results, "similarity"), results);
10705
+ const results = await findSimilarCode(projectRoot2(ctx), HOST2, params.code, params);
10706
+ return text2(formatSearchResults(results, "similarity"), results);
9427
10707
  }
9428
10708
  });
9429
10709
  pi.registerTool({
9430
10710
  name: "implementation_lookup",
9431
10711
  label: "Implementation Lookup",
9432
10712
  description: "Find likely symbol definitions or implementations by name or natural language.",
9433
- parameters: Type.Object({
9434
- query: Type.String(),
9435
- limit: Type.Optional(Type.Number()),
9436
- fileType: Type.Optional(Type.String()),
9437
- directory: Type.Optional(Type.String())
10713
+ parameters: Type2.Object({
10714
+ query: Type2.String(),
10715
+ limit: Type2.Optional(Type2.Number()),
10716
+ fileType: Type2.Optional(Type2.String()),
10717
+ directory: Type2.Optional(Type2.String())
9438
10718
  }),
9439
10719
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9440
- const results = await implementationLookup(projectRoot(ctx), HOST, params.query, params);
9441
- return text(formatDefinitionLookup(results, params.query), results);
10720
+ const results = await implementationLookup(projectRoot2(ctx), HOST2, params.query, params);
10721
+ return text2(formatDefinitionLookup(results, params.query), results);
9442
10722
  }
9443
10723
  });
9444
10724
  pi.registerTool({
9445
10725
  name: "index_codebase",
9446
10726
  label: "Index Codebase",
9447
10727
  description: "Build or refresh the semantic codebase index.",
9448
- parameters: Type.Object({
9449
- force: Type.Optional(Type.Boolean({ default: false })),
9450
- estimateOnly: Type.Optional(Type.Boolean({ default: false })),
9451
- verbose: Type.Optional(Type.Boolean({ default: false }))
10728
+ parameters: Type2.Object({
10729
+ force: Type2.Optional(Type2.Boolean({ default: false })),
10730
+ estimateOnly: Type2.Optional(Type2.Boolean({ default: false })),
10731
+ verbose: Type2.Optional(Type2.Boolean({ default: false }))
9452
10732
  }),
9453
10733
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9454
- const result = await runIndexCodebase(projectRoot(ctx), HOST, params);
9455
- return result.kind === "estimate" ? text(formatCostEstimate(result.estimate), result.estimate) : text(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
10734
+ const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
10735
+ if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
10736
+ if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
10737
+ return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
9456
10738
  }
9457
10739
  });
9458
10740
  pi.registerTool({
9459
10741
  name: "index_status",
9460
10742
  label: "Index Status",
9461
10743
  description: "Check index health and current status.",
9462
- parameters: Type.Object({}),
10744
+ parameters: Type2.Object({}),
9463
10745
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9464
- const status = await getIndexStatus(projectRoot(ctx), HOST);
9465
- return text(formatStatus(status), status);
10746
+ const status = await getIndexStatus(projectRoot2(ctx), HOST2);
10747
+ return text2(formatStatus(status), status);
9466
10748
  }
9467
10749
  });
9468
10750
  pi.registerTool({
9469
10751
  name: "index_health_check",
9470
10752
  label: "Index Health Check",
9471
10753
  description: "Garbage collect orphaned embeddings/chunks and report health.",
9472
- parameters: Type.Object({}),
10754
+ parameters: Type2.Object({}),
9473
10755
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9474
- const result = await getIndexHealthCheck(projectRoot(ctx), HOST);
9475
- return text(formatHealthCheck(result), result);
10756
+ const result = await runIndexHealthCheck(projectRoot2(ctx), HOST2);
10757
+ if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
10758
+ return text2(formatHealthCheck(result.health), result.health);
9476
10759
  }
9477
10760
  });
9478
10761
  pi.registerTool({
9479
10762
  name: "index_metrics",
9480
10763
  label: "Index Metrics",
9481
10764
  description: "Return collected performance metrics when debug metrics are enabled.",
9482
- parameters: Type.Object({}),
10765
+ parameters: Type2.Object({}),
9483
10766
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9484
- const result = await getIndexMetrics(projectRoot(ctx), HOST);
9485
- return text(result.text, result);
10767
+ const result = await getIndexMetrics(projectRoot2(ctx), HOST2);
10768
+ return text2(result.text, result);
9486
10769
  }
9487
10770
  });
9488
10771
  pi.registerTool({
9489
10772
  name: "index_logs",
9490
10773
  label: "Index Logs",
9491
10774
  description: "Return recent debug logs when debug logging is enabled.",
9492
- parameters: Type.Object({
9493
- limit: Type.Optional(Type.Number()),
9494
- category: Type.Optional(Type.Union([
9495
- Type.Literal("search"),
9496
- Type.Literal("embedding"),
9497
- Type.Literal("cache"),
9498
- Type.Literal("gc"),
9499
- Type.Literal("branch"),
9500
- Type.Literal("general")
10775
+ parameters: Type2.Object({
10776
+ limit: Type2.Optional(Type2.Number()),
10777
+ category: Type2.Optional(Type2.Union([
10778
+ Type2.Literal("search"),
10779
+ Type2.Literal("embedding"),
10780
+ Type2.Literal("cache"),
10781
+ Type2.Literal("gc"),
10782
+ Type2.Literal("branch"),
10783
+ Type2.Literal("general")
9501
10784
  ])),
9502
- level: Type.Optional(Type.Union([Type.Literal("error"), Type.Literal("warn"), Type.Literal("info"), Type.Literal("debug")]))
9503
- }),
9504
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9505
- const result = await getIndexLogs(projectRoot(ctx), HOST, params);
9506
- return text(result.text, result);
9507
- }
9508
- });
9509
- pi.registerTool({
9510
- name: "call_graph",
9511
- label: "Call Graph",
9512
- description: "Find callers or callees of a function/method in the indexed call graph.",
9513
- parameters: Type.Object({
9514
- name: Type.String(),
9515
- direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees")])),
9516
- symbolId: Type.Optional(Type.String()),
9517
- relationshipType: Type.Optional(RelationshipType)
10785
+ level: Type2.Optional(Type2.Union([Type2.Literal("error"), Type2.Literal("warn"), Type2.Literal("info"), Type2.Literal("debug")]))
9518
10786
  }),
9519
10787
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9520
- const result = await getCallGraphData(projectRoot(ctx), HOST, params);
9521
- return text(JSON.stringify(result, null, 2), result);
9522
- }
9523
- });
9524
- pi.registerTool({
9525
- name: "call_graph_path",
9526
- label: "Call Graph Path",
9527
- description: "Find a call path between two functions/methods.",
9528
- parameters: Type.Object({
9529
- from: Type.String(),
9530
- to: Type.String(),
9531
- maxDepth: Type.Optional(Type.Number({ default: 10 }))
9532
- }),
9533
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9534
- const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
9535
- return text(JSON.stringify(result, null, 2), result);
10788
+ const result = await getIndexLogs(projectRoot2(ctx), HOST2, params);
10789
+ return text2(result.text, result);
9536
10790
  }
9537
10791
  });
10792
+ registerPiCallGraphTools(pi);
9538
10793
  pi.registerTool({
9539
10794
  name: "pr_impact",
9540
10795
  label: "PR Impact",
9541
10796
  description: "Analyze PR or branch impact through changed symbols and call graph neighborhoods.",
9542
- parameters: Type.Object({
9543
- pr: Type.Optional(Type.Number()),
9544
- branch: Type.Optional(Type.String()),
9545
- maxDepth: Type.Optional(Type.Number({ default: 5 })),
9546
- hubThreshold: Type.Optional(Type.Number({ default: 10 })),
9547
- checkConflicts: Type.Optional(Type.Boolean({ default: false })),
9548
- direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees"), Type.Literal("both")]))
10797
+ parameters: Type2.Object({
10798
+ pr: Type2.Optional(Type2.Number()),
10799
+ branch: Type2.Optional(Type2.String()),
10800
+ maxDepth: Type2.Optional(Type2.Number({ default: 5 })),
10801
+ hubThreshold: Type2.Optional(Type2.Number({ default: 10 })),
10802
+ checkConflicts: Type2.Optional(Type2.Boolean({ default: false })),
10803
+ direction: Type2.Optional(Type2.Union([Type2.Literal("callers"), Type2.Literal("callees"), Type2.Literal("both")]))
9549
10804
  }),
9550
10805
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9551
- const result = await getPrImpact(projectRoot(ctx), HOST, params);
9552
- return text(formatPrImpact(result), result);
10806
+ const result = await getPrImpact(projectRoot2(ctx), HOST2, params);
10807
+ return text2(formatPrImpact(result), result);
9553
10808
  }
9554
10809
  });
9555
10810
  pi.registerTool({
9556
10811
  name: "knowledge_base_list",
9557
10812
  label: "List Knowledge Bases",
9558
10813
  description: "List configured knowledge-base paths included in the index.",
9559
- parameters: Type.Object({}),
10814
+ parameters: Type2.Object({}),
9560
10815
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9561
- return text(listKnowledgeBases(projectRoot(ctx), HOST));
10816
+ return text2(listKnowledgeBases(projectRoot2(ctx), HOST2));
9562
10817
  }
9563
10818
  });
9564
10819
  pi.registerTool({
9565
10820
  name: "knowledge_base_add",
9566
10821
  label: "Add Knowledge Base",
9567
10822
  description: "Add a knowledge-base path to the codebase index config.",
9568
- parameters: Type.Object({ path: Type.String({ description: "File or directory path to add" }) }),
10823
+ parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to add" }) }),
9569
10824
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9570
- return text(addKnowledgeBase(projectRoot(ctx), HOST, params.path));
10825
+ return text2(addKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9571
10826
  }
9572
10827
  });
9573
10828
  pi.registerTool({
9574
10829
  name: "knowledge_base_remove",
9575
10830
  label: "Remove Knowledge Base",
9576
10831
  description: "Remove a knowledge-base path from the codebase index config.",
9577
- parameters: Type.Object({ path: Type.String({ description: "File or directory path to remove" }) }),
10832
+ parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to remove" }) }),
9578
10833
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9579
- return text(removeKnowledgeBase(projectRoot(ctx), HOST, params.path));
10834
+ return text2(removeKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9580
10835
  }
9581
10836
  });
9582
10837
  }