opencode-codebase-index 0.13.1 → 0.14.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(path16, 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(path16);
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 = (path16, originalPath, doThrow) => {
524
+ if (!isString(path16)) {
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 (!path16) {
531
531
  return doThrow(`path must not be empty`, TypeError);
532
532
  }
533
- if (checkPath.isNotRelative(path15)) {
533
+ if (checkPath.isNotRelative(path16)) {
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 = (path16) => REGEX_TEST_INVALID_PATH.test(path16);
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 path16 = originalPath && checkPath.convert(originalPath);
573
573
  checkPath(
574
- path15,
574
+ path16,
575
575
  originalPath,
576
576
  this._strictPathCheck ? throwError : RETURN_FALSE
577
577
  );
578
- return this._t(path15, cache, checkUnignored, slices);
578
+ return this._t(path16, cache, checkUnignored, slices);
579
579
  }
580
- checkIgnore(path15) {
581
- if (!REGEX_TEST_TRAILING_SLASH.test(path15)) {
582
- return this.test(path15);
580
+ checkIgnore(path16) {
581
+ if (!REGEX_TEST_TRAILING_SLASH.test(path16)) {
582
+ return this.test(path16);
583
583
  }
584
- const slices = path15.split(SLASH).filter(Boolean);
584
+ const slices = path16.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(path16, false, MODE_CHECK_IGNORE);
598
598
  }
599
- _t(path15, cache, checkUnignored, slices) {
600
- if (path15 in cache) {
601
- return cache[path15];
599
+ _t(path16, cache, checkUnignored, slices) {
600
+ if (path16 in cache) {
601
+ return cache[path16];
602
602
  }
603
603
  if (!slices) {
604
- slices = path15.split(SLASH).filter(Boolean);
604
+ slices = path16.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[path16] = this._rules.test(path16, 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[path16] = parent.ignored ? parent : this._rules.test(path16, checkUnignored, MODE_IGNORE);
617
617
  }
618
- ignores(path15) {
619
- return this._test(path15, this._ignoreCache, false).ignored;
618
+ ignores(path16) {
619
+ return this._test(path16, this._ignoreCache, false).ignored;
620
620
  }
621
621
  createFilter() {
622
- return (path15) => !this.ignores(path15);
622
+ return (path16) => !this.ignores(path16);
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(path16) {
629
+ return this._test(path16, 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 = (path16) => checkPath(path16 && checkPath.convert(path16), path16, 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 = (path16) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path16) || isNotRelative(path16);
639
639
  };
640
640
  if (
641
641
  // Detect `process` so that it can run in browsers.
@@ -651,7 +651,7 @@ 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 = [
@@ -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 = {
@@ -1324,7 +1328,7 @@ function formatPrImpact(result) {
1324
1328
 
1325
1329
  // src/tools/operations.ts
1326
1330
  import { existsSync as existsSync9, realpathSync, statSync as statSync3 } from "fs";
1327
- import * as path14 from "path";
1331
+ import * as path15 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,8 @@ 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)));
1523
1527
  }
1524
1528
  function getGlobalIndexPath(host = "opencode") {
1525
1529
  switch (host) {
@@ -1567,55 +1571,55 @@ function resolveGlobalIndexPath(host = "opencode") {
1567
1571
  }
1568
1572
  return hostIndexPath;
1569
1573
  }
1570
- function resolveProjectConfigPath(projectRoot2, host = "opencode") {
1571
- const hostConfigPath = path4.join(projectRoot2, getProjectConfigRelativePath(host));
1574
+ function resolveProjectConfigPath(projectRoot3, host = "opencode") {
1575
+ const hostConfigPath = path4.join(projectRoot3, getProjectConfigRelativePath(host));
1572
1576
  if (existsSync4(hostConfigPath)) {
1573
1577
  return hostConfigPath;
1574
1578
  }
1575
1579
  if (host !== "opencode") {
1576
- const legacyConfigPath = path4.join(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1580
+ const legacyConfigPath = path4.join(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1577
1581
  if (existsSync4(legacyConfigPath)) {
1578
1582
  return legacyConfigPath;
1579
1583
  }
1580
1584
  }
1581
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectConfigRelativePath(host));
1585
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectConfigRelativePath(host));
1582
1586
  if (hostFallback) {
1583
1587
  return hostFallback;
1584
1588
  }
1585
1589
  if (host !== "opencode") {
1586
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1590
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
1587
1591
  if (legacyFallback) {
1588
1592
  return legacyFallback;
1589
1593
  }
1590
1594
  }
1591
1595
  return hostConfigPath;
1592
1596
  }
1593
- function resolveWritableProjectConfigPath(projectRoot2, host = "opencode") {
1594
- return path4.join(projectRoot2, getProjectConfigRelativePath(host));
1597
+ function resolveWritableProjectConfigPath(projectRoot3, host = "opencode") {
1598
+ return path4.join(projectRoot3, getProjectConfigRelativePath(host));
1595
1599
  }
1596
- function resolveProjectIndexPath(projectRoot2, scope, host = "opencode") {
1600
+ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1597
1601
  if (scope === "global") {
1598
1602
  return resolveGlobalIndexPath(host);
1599
1603
  }
1600
- const localIndexPath = path4.join(projectRoot2, getProjectIndexRelativePath(host));
1604
+ const localIndexPath = path4.join(projectRoot3, getProjectIndexRelativePath(host));
1601
1605
  if (existsSync4(localIndexPath)) {
1602
1606
  return localIndexPath;
1603
1607
  }
1604
1608
  if (host !== "opencode") {
1605
- const legacyIndexPath = path4.join(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1606
- if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(projectRoot2, host)) {
1609
+ const legacyIndexPath = path4.join(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1610
+ if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(projectRoot3, host)) {
1607
1611
  return legacyIndexPath;
1608
1612
  }
1609
1613
  }
1610
- if (hasHostProjectConfig(projectRoot2, host)) {
1614
+ if (hasHostProjectConfig(projectRoot3, host)) {
1611
1615
  return localIndexPath;
1612
1616
  }
1613
- const hostFallback = resolveWorktreeFallbackPath(projectRoot2, getProjectIndexRelativePath(host));
1617
+ const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1614
1618
  if (hostFallback) {
1615
1619
  return hostFallback;
1616
1620
  }
1617
1621
  if (host !== "opencode") {
1618
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot2, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1622
+ const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1619
1623
  if (legacyFallback) {
1620
1624
  return legacyFallback;
1621
1625
  }
@@ -1748,14 +1752,14 @@ function loadJsonFile(filePath) {
1748
1752
  throw new Error(`Failed to load config file ${filePath}: ${message}`);
1749
1753
  }
1750
1754
  }
1751
- function materializeLocalProjectConfig(projectRoot2, config, host = "opencode") {
1752
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot2, host);
1755
+ function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
1756
+ const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
1753
1757
  mkdirSync(path7.dirname(localConfigPath), { recursive: true });
1754
1758
  writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1755
1759
  return localConfigPath;
1756
1760
  }
1757
- function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1758
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1761
+ function loadProjectConfigLayer(projectRoot3, host = "opencode") {
1762
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1759
1763
  const projectConfig = loadJsonFile(projectConfigPath);
1760
1764
  if (!projectConfig) {
1761
1765
  return {};
@@ -1766,14 +1770,14 @@ function loadProjectConfigLayer(projectRoot2, host = "opencode") {
1766
1770
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1767
1771
  normalizedConfig.knowledgeBases,
1768
1772
  projectConfigBaseDir,
1769
- projectRoot2
1773
+ projectRoot3
1770
1774
  );
1771
1775
  }
1772
1776
  return normalizedConfig;
1773
1777
  }
1774
- function loadMergedConfig(projectRoot2, host = "opencode") {
1778
+ function loadMergedConfig(projectRoot3, host = "opencode") {
1775
1779
  const globalConfigPath = resolveGlobalConfigPath(host);
1776
- const projectConfigPath = resolveProjectConfigPath(projectRoot2, host);
1780
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1777
1781
  let globalConfig = null;
1778
1782
  let globalConfigError = null;
1779
1783
  try {
@@ -1782,7 +1786,7 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1782
1786
  globalConfigError = error instanceof Error ? error : new Error(String(error));
1783
1787
  }
1784
1788
  const projectConfig = loadJsonFile(projectConfigPath);
1785
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot2, host);
1789
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
1786
1790
  if (globalConfigError) {
1787
1791
  if (!projectConfig) {
1788
1792
  throw globalConfigError;
@@ -1826,10 +1830,10 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
1826
1830
 
1827
1831
  // src/indexer/index.ts
1828
1832
  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";
1833
+ import * as path12 from "path";
1830
1834
  import { performance as performance2 } from "perf_hooks";
1831
- import { execFile as execFile2 } from "child_process";
1832
- import { promisify as promisify2 } from "util";
1835
+ import { execFile as execFile3 } from "child_process";
1836
+ import { promisify as promisify3 } from "util";
1833
1837
 
1834
1838
  // node_modules/eventemitter3/index.mjs
1835
1839
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -3137,10 +3141,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3137
3141
  }
3138
3142
  const batchResults = await Promise.all(
3139
3143
  batches.map(async (batch) => {
3140
- const requests = batch.map((text2) => ({
3144
+ const requests = batch.map((text3) => ({
3141
3145
  model: `models/${this.modelInfo.model}`,
3142
3146
  content: {
3143
- parts: [{ text: text2 }]
3147
+ parts: [{ text: text3 }]
3144
3148
  },
3145
3149
  taskType,
3146
3150
  outputDimensionality: this.modelInfo.dimensions
@@ -3163,7 +3167,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3163
3167
  const data = await response.json();
3164
3168
  return {
3165
3169
  embeddings: data.embeddings.map((e) => e.values),
3166
- tokensUsed: batch.reduce((sum, text2) => sum + Math.ceil(text2.length / 4), 0)
3170
+ tokensUsed: batch.reduce((sum, text3) => sum + Math.ceil(text3.length / 4), 0)
3167
3171
  };
3168
3172
  })
3169
3173
  );
@@ -3180,28 +3184,28 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3180
3184
  constructor(credentials, modelInfo) {
3181
3185
  super(credentials, modelInfo);
3182
3186
  }
3183
- estimateTokens(text2) {
3184
- return Math.ceil(text2.length / 4);
3187
+ estimateTokens(text3) {
3188
+ return Math.ceil(text3.length / 4);
3185
3189
  }
3186
- truncateToCharLimit(text2, maxChars) {
3187
- if (text2.length <= maxChars) {
3188
- return text2;
3190
+ truncateToCharLimit(text3, maxChars) {
3191
+ if (text3.length <= maxChars) {
3192
+ return text3;
3189
3193
  }
3190
- return `${text2.slice(0, Math.max(0, maxChars - 17))}
3194
+ return `${text3.slice(0, Math.max(0, maxChars - 17))}
3191
3195
  ... [truncated]`;
3192
3196
  }
3193
3197
  isContextLengthError(error) {
3194
3198
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
3195
3199
  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
3200
  }
3197
- buildTruncationCandidates(text2) {
3201
+ buildTruncationCandidates(text3) {
3198
3202
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
3199
3203
  const candidateLimits = /* @__PURE__ */ new Set();
3200
- const baselineLimit = text2.length > baseMaxChars ? baseMaxChars : Math.max(
3204
+ const baselineLimit = text3.length > baseMaxChars ? baseMaxChars : Math.max(
3201
3205
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3202
- Math.floor(text2.length * 0.9)
3206
+ Math.floor(text3.length * 0.9)
3203
3207
  );
3204
- if (baselineLimit < text2.length) {
3208
+ if (baselineLimit < text3.length) {
3205
3209
  candidateLimits.add(baselineLimit);
3206
3210
  }
3207
3211
  for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
@@ -3209,19 +3213,19 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3209
3213
  _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
3210
3214
  Math.floor(baselineLimit * factor)
3211
3215
  );
3212
- if (scaledLimit < text2.length) {
3216
+ if (scaledLimit < text3.length) {
3213
3217
  candidateLimits.add(scaledLimit);
3214
3218
  }
3215
3219
  }
3216
- candidateLimits.add(Math.min(text2.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3220
+ candidateLimits.add(Math.min(text3.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
3217
3221
  const candidates = [];
3218
3222
  const seen = /* @__PURE__ */ new Set();
3219
3223
  for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
3220
- if (limit <= 0 || limit >= text2.length) {
3224
+ if (limit <= 0 || limit >= text3.length) {
3221
3225
  continue;
3222
3226
  }
3223
- const truncated = this.truncateToCharLimit(text2, limit);
3224
- if (truncated === text2 || seen.has(truncated)) {
3227
+ const truncated = this.truncateToCharLimit(text3, limit);
3228
+ if (truncated === text3 || seen.has(truncated)) {
3225
3229
  continue;
3226
3230
  }
3227
3231
  seen.add(truncated);
@@ -3229,15 +3233,15 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3229
3233
  }
3230
3234
  return candidates;
3231
3235
  }
3232
- async embedSingleWithFallback(text2) {
3236
+ async embedSingleWithFallback(text3) {
3233
3237
  try {
3234
- return await this.embedSingle(text2);
3238
+ return await this.embedSingle(text3);
3235
3239
  } catch (error) {
3236
3240
  if (!this.isContextLengthError(error)) {
3237
3241
  throw error;
3238
3242
  }
3239
3243
  let lastError = error;
3240
- for (const truncated of this.buildTruncationCandidates(text2)) {
3244
+ for (const truncated of this.buildTruncationCandidates(text3)) {
3241
3245
  try {
3242
3246
  return await this.embedSingle(truncated);
3243
3247
  } catch (retryError) {
@@ -3250,7 +3254,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3250
3254
  throw lastError;
3251
3255
  }
3252
3256
  }
3253
- async embedSingle(text2) {
3257
+ async embedSingle(text3) {
3254
3258
  const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3255
3259
  method: "POST",
3256
3260
  headers: {
@@ -3258,7 +3262,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3258
3262
  },
3259
3263
  body: JSON.stringify({
3260
3264
  model: this.modelInfo.model,
3261
- prompt: text2,
3265
+ prompt: text3,
3262
3266
  truncate: false
3263
3267
  })
3264
3268
  });
@@ -3269,13 +3273,13 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3269
3273
  const data = await response.json();
3270
3274
  return {
3271
3275
  embedding: data.embedding,
3272
- tokensUsed: this.estimateTokens(text2)
3276
+ tokensUsed: this.estimateTokens(text3)
3273
3277
  };
3274
3278
  }
3275
3279
  async embedBatch(texts) {
3276
3280
  const results = [];
3277
- for (const text2 of texts) {
3278
- results.push(await this.embedSingleWithFallback(text2));
3281
+ for (const text3 of texts) {
3282
+ results.push(await this.embedSingleWithFallback(text3));
3279
3283
  }
3280
3284
  return {
3281
3285
  embeddings: results.map((r) => r.embedding),
@@ -3416,7 +3420,7 @@ var SiliconFlowReranker = class {
3416
3420
  var import_ignore = __toESM(require_ignore(), 1);
3417
3421
  import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
3418
3422
  import * as path8 from "path";
3419
- function createIgnoreFilter(projectRoot2) {
3423
+ function createIgnoreFilter(projectRoot3) {
3420
3424
  const ig = (0, import_ignore.default)();
3421
3425
  const defaultIgnores = [
3422
3426
  "node_modules",
@@ -3437,7 +3441,7 @@ function createIgnoreFilter(projectRoot2) {
3437
3441
  "**/*build*/**"
3438
3442
  ];
3439
3443
  ig.add(defaultIgnores);
3440
- const gitignorePath = path8.join(projectRoot2, ".gitignore");
3444
+ const gitignorePath = path8.join(projectRoot3, ".gitignore");
3441
3445
  if (existsSync6(gitignorePath)) {
3442
3446
  const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
3443
3447
  ig.add(gitignoreContent);
@@ -3459,13 +3463,13 @@ function matchGlob(filePath, pattern) {
3459
3463
  const regex = new RegExp(`^${regexPattern}$`);
3460
3464
  return regex.test(filePath);
3461
3465
  }
3462
- async function* walkDirectory(dir, projectRoot2, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3466
+ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3463
3467
  const entries = await fsPromises.readdir(dir, { withFileTypes: true });
3464
3468
  const filesInDir = [];
3465
3469
  const subdirs = [];
3466
3470
  for (const entry of entries) {
3467
3471
  const fullPath = path8.join(dir, entry.name);
3468
- const relativePath = path8.relative(projectRoot2, fullPath);
3472
+ const relativePath = path8.relative(projectRoot3, fullPath);
3469
3473
  if (isHiddenPathSegment(entry.name)) {
3470
3474
  if (entry.isDirectory()) {
3471
3475
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3514,14 +3518,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3514
3518
  yield f;
3515
3519
  }
3516
3520
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3517
- skipped.push({ path: path8.relative(projectRoot2, filesInDir[i].path), reason: "excluded" });
3521
+ skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3518
3522
  }
3519
3523
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3520
3524
  if (canRecurse) {
3521
3525
  for (const sub of subdirs) {
3522
3526
  yield* walkDirectory(
3523
3527
  sub.fullPath,
3524
- projectRoot2,
3528
+ projectRoot3,
3525
3529
  includePatterns,
3526
3530
  excludePatterns,
3527
3531
  ignoreFilter,
@@ -3533,14 +3537,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
3533
3537
  }
3534
3538
  }
3535
3539
  }
3536
- async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3540
+ async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
3537
3541
  const opts = walkOptions ?? { maxDepth: 5, maxFilesPerDirectory: 100 };
3538
- const ignoreFilter = createIgnoreFilter(projectRoot2);
3542
+ const ignoreFilter = createIgnoreFilter(projectRoot3);
3539
3543
  const files = [];
3540
3544
  const skipped = [];
3541
3545
  for await (const file of walkDirectory(
3542
- projectRoot2,
3543
- projectRoot2,
3546
+ projectRoot3,
3547
+ projectRoot3,
3544
3548
  includePatterns,
3545
3549
  excludePatterns,
3546
3550
  ignoreFilter,
@@ -3555,7 +3559,7 @@ async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxF
3555
3559
  const normalizedRoots = /* @__PURE__ */ new Set();
3556
3560
  for (const kbRoot of additionalRoots) {
3557
3561
  const resolved = path8.normalize(
3558
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot2, kbRoot)
3562
+ path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
3559
3563
  );
3560
3564
  normalizedRoots.add(resolved);
3561
3565
  }
@@ -4121,8 +4125,8 @@ var VectorStore = class {
4121
4125
  var CHARS_PER_TOKEN = 4;
4122
4126
  var MAX_BATCH_TOKENS = 7500;
4123
4127
  var MAX_SINGLE_CHUNK_TOKENS = 2e3;
4124
- function estimateTokens(text2) {
4125
- return Math.ceil(text2.length / CHARS_PER_TOKEN);
4128
+ function estimateTokens(text3) {
4129
+ return Math.ceil(text3.length / CHARS_PER_TOKEN);
4126
4130
  }
4127
4131
  function getEmbeddingHeaderParts(chunk, filePath) {
4128
4132
  const parts = [];
@@ -4656,20 +4660,20 @@ function getErrorMessage(error) {
4656
4660
  return String(error);
4657
4661
  }
4658
4662
  async function getChangedFiles(opts) {
4659
- const { pr, branch, projectRoot: projectRoot2, baseBranch = "main" } = opts;
4663
+ const { pr, branch, projectRoot: projectRoot3, baseBranch = "main" } = opts;
4660
4664
  if (pr !== void 0) {
4661
- return getChangedFilesForPr(pr, projectRoot2, baseBranch);
4665
+ return getChangedFilesForPr(pr, projectRoot3, baseBranch);
4662
4666
  }
4663
- return getChangedFilesForBranch(branch, projectRoot2, baseBranch);
4667
+ return getChangedFilesForBranch(branch, projectRoot3, baseBranch);
4664
4668
  }
4665
- async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4669
+ async function getChangedFilesForPr(pr, projectRoot3, baseBranch) {
4666
4670
  let headRefName;
4667
4671
  let actualBaseBranch = baseBranch;
4668
4672
  try {
4669
4673
  const { stdout } = await execFileAsync(
4670
4674
  "gh",
4671
4675
  ["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
4672
- { cwd: projectRoot2, timeout: 3e4 }
4676
+ { cwd: projectRoot3, timeout: 3e4 }
4673
4677
  );
4674
4678
  const data = JSON.parse(stdout);
4675
4679
  headRefName = data.headRefName;
@@ -4678,7 +4682,7 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4678
4682
  return {
4679
4683
  files: normalizeFiles(
4680
4684
  data.files.map((f) => f.path),
4681
- projectRoot2
4685
+ projectRoot3
4682
4686
  ),
4683
4687
  baseBranch: actualBaseBranch,
4684
4688
  source: "gh",
@@ -4695,49 +4699,49 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
4695
4699
  `PR #${pr} returned no usable branch or file information.`
4696
4700
  );
4697
4701
  }
4698
- const result = await getChangedFilesForBranch(headRefName, projectRoot2, actualBaseBranch);
4702
+ const result = await getChangedFilesForBranch(headRefName, projectRoot3, actualBaseBranch);
4699
4703
  return { ...result, headRefName };
4700
4704
  }
4701
- async function getChangedFilesForBranch(branch, projectRoot2, baseBranch) {
4702
- const targetBranch = branch || await getCurrentBranch2(projectRoot2);
4703
- const mergeBase = await getMergeBase(projectRoot2, baseBranch, targetBranch);
4705
+ async function getChangedFilesForBranch(branch, projectRoot3, baseBranch) {
4706
+ const targetBranch = branch || await getCurrentBranch2(projectRoot3);
4707
+ const mergeBase = await getMergeBase(projectRoot3, baseBranch, targetBranch);
4704
4708
  const { stdout } = await execFileAsync(
4705
4709
  "git",
4706
4710
  ["diff", "--name-only", `${mergeBase}...${targetBranch}`],
4707
- { cwd: projectRoot2, timeout: 3e4 }
4711
+ { cwd: projectRoot3, timeout: 3e4 }
4708
4712
  );
4709
4713
  return {
4710
- files: normalizeFiles(stdout.split("\n"), projectRoot2),
4714
+ files: normalizeFiles(stdout.split("\n"), projectRoot3),
4711
4715
  baseBranch,
4712
4716
  source: "git",
4713
4717
  headRefName: targetBranch
4714
4718
  };
4715
4719
  }
4716
- async function getCurrentBranch2(projectRoot2) {
4720
+ async function getCurrentBranch2(projectRoot3) {
4717
4721
  const { stdout } = await execFileAsync(
4718
4722
  "git",
4719
4723
  ["branch", "--show-current"],
4720
- { cwd: projectRoot2, timeout: 3e4 }
4724
+ { cwd: projectRoot3, timeout: 3e4 }
4721
4725
  );
4722
4726
  return stdout.trim() || "HEAD";
4723
4727
  }
4724
- async function getMergeBase(projectRoot2, baseBranch, branch) {
4728
+ async function getMergeBase(projectRoot3, baseBranch, branch) {
4725
4729
  const { stdout } = await execFileAsync(
4726
4730
  "git",
4727
4731
  ["merge-base", baseBranch, branch],
4728
- { cwd: projectRoot2, timeout: 3e4 }
4732
+ { cwd: projectRoot3, timeout: 3e4 }
4729
4733
  );
4730
4734
  return stdout.trim();
4731
4735
  }
4732
- function normalizeFiles(rawFiles, projectRoot2) {
4736
+ function normalizeFiles(rawFiles, projectRoot3) {
4733
4737
  const seen = /* @__PURE__ */ new Set();
4734
4738
  const result = [];
4735
4739
  for (const raw of rawFiles) {
4736
4740
  const trimmed = raw.trim();
4737
4741
  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;
4742
+ const absolute = path10.resolve(projectRoot3, trimmed);
4743
+ const relative7 = path10.relative(projectRoot3, absolute);
4744
+ const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
4741
4745
  if (!seen.has(cleaned)) {
4742
4746
  seen.add(cleaned);
4743
4747
  result.push(cleaned);
@@ -4746,8 +4750,61 @@ function normalizeFiles(rawFiles, projectRoot2) {
4746
4750
  return result;
4747
4751
  }
4748
4752
 
4753
+ // src/indexer/git-blame.ts
4754
+ import { execFile as execFile2 } from "child_process";
4755
+ import * as path11 from "path";
4756
+ import { promisify as promisify2 } from "util";
4757
+ var execFileAsync2 = promisify2(execFile2);
4758
+ function parseGitBlamePorcelain(output) {
4759
+ const commits = /* @__PURE__ */ new Map();
4760
+ let current;
4761
+ for (const line of output.split("\n")) {
4762
+ if (/^[0-9a-f]{40} /.test(line)) {
4763
+ const sha = line.slice(0, 40);
4764
+ current = commits.get(sha) ?? {
4765
+ sha,
4766
+ author: "",
4767
+ authorEmail: "",
4768
+ committedAt: 0,
4769
+ summary: "",
4770
+ lines: 0
4771
+ };
4772
+ commits.set(sha, current);
4773
+ continue;
4774
+ }
4775
+ if (!current) {
4776
+ continue;
4777
+ }
4778
+ if (line.startsWith("author ")) {
4779
+ current.author = line.slice("author ".length);
4780
+ } else if (line.startsWith("author-mail ")) {
4781
+ current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
4782
+ } else if (line.startsWith("author-time ")) {
4783
+ current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
4784
+ } else if (line.startsWith("summary ")) {
4785
+ current.summary = line.slice("summary ".length);
4786
+ } else if (line.startsWith(" ")) {
4787
+ current.lines += 1;
4788
+ }
4789
+ }
4790
+ return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4791
+ }
4792
+ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4793
+ const relativePath = path11.relative(projectRoot3, filePath);
4794
+ try {
4795
+ const { stdout } = await execFileAsync2(
4796
+ "git",
4797
+ ["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
4798
+ { cwd: projectRoot3, timeout: 3e4 }
4799
+ );
4800
+ return parseGitBlamePorcelain(stdout);
4801
+ } catch {
4802
+ return void 0;
4803
+ }
4804
+ }
4805
+
4749
4806
  // src/indexer/index.ts
4750
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
4807
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4751
4808
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
4752
4809
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
4753
4810
  "function_declaration",
@@ -4827,6 +4884,45 @@ function isSqliteCorruptionError(error) {
4827
4884
  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
4885
  }
4829
4886
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
4887
+ function metadataFromBlame(blame) {
4888
+ if (!blame) {
4889
+ return {};
4890
+ }
4891
+ return {
4892
+ blameSha: blame.sha,
4893
+ blameAuthor: blame.author,
4894
+ blameAuthorEmail: blame.authorEmail,
4895
+ blameCommittedAt: blame.committedAt,
4896
+ blameSummary: blame.summary
4897
+ };
4898
+ }
4899
+ function blameFromChunkData(chunk) {
4900
+ if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
4901
+ return void 0;
4902
+ }
4903
+ return {
4904
+ sha: chunk.blameSha,
4905
+ author: chunk.blameAuthor,
4906
+ authorEmail: chunk.blameAuthorEmail,
4907
+ committedAt: chunk.blameCommittedAt,
4908
+ summary: chunk.blameSummary
4909
+ };
4910
+ }
4911
+ function blameFromMetadata(metadata) {
4912
+ if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
4913
+ return void 0;
4914
+ }
4915
+ return {
4916
+ sha: metadata.blameSha,
4917
+ author: metadata.blameAuthor,
4918
+ authorEmail: metadata.blameAuthorEmail,
4919
+ committedAt: metadata.blameCommittedAt,
4920
+ summary: metadata.blameSummary
4921
+ };
4922
+ }
4923
+ function hasBlameMetadata(metadata) {
4924
+ return blameFromMetadata(metadata) !== void 0;
4925
+ }
4830
4926
  var INDEX_METADATA_VERSION = "1";
4831
4927
  var EMBEDDING_STRATEGY_VERSION = "2";
4832
4928
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
@@ -4870,9 +4966,9 @@ function normalizePendingChunk(rawChunk, maxChunkTokens) {
4870
4966
  };
4871
4967
  const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
4872
4968
  texts.push(
4873
- ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text2) => ({
4874
- text: text2,
4875
- tokenCount: estimateTokens(text2)
4969
+ ...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text3) => ({
4970
+ text: text3,
4971
+ tokenCount: estimateTokens(text3)
4876
4972
  }))
4877
4973
  );
4878
4974
  } else {
@@ -4985,9 +5081,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
4985
5081
  return true;
4986
5082
  }
4987
5083
  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}`);
5084
+ const normalizedFilePath = path12.resolve(filePath);
5085
+ const normalizedRoot = path12.resolve(rootPath);
5086
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
4991
5087
  }
4992
5088
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
4993
5089
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5103,11 +5199,11 @@ function setBoundedCache(cache, key, value) {
5103
5199
  }
5104
5200
  cache.set(key, value);
5105
5201
  }
5106
- function tokenizeTextForRanking(text2) {
5107
- if (!text2) {
5202
+ function tokenizeTextForRanking(text3) {
5203
+ if (!text3) {
5108
5204
  return /* @__PURE__ */ new Set();
5109
5205
  }
5110
- const lowered = text2.toLowerCase();
5206
+ const lowered = text3.toLowerCase();
5111
5207
  const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
5112
5208
  if (cache) {
5113
5209
  return cache;
@@ -5746,7 +5842,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
5746
5842
  chunkType,
5747
5843
  name: chunk.name ?? void 0,
5748
5844
  language: chunk.language,
5749
- hash: chunk.contentHash
5845
+ hash: chunk.contentHash,
5846
+ ...metadataFromBlame(blameFromChunkData(chunk))
5750
5847
  };
5751
5848
  const baselineScore = existing?.score ?? 0.5;
5752
5849
  candidateUnion.set(chunk.chunkId, {
@@ -5830,7 +5927,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
5830
5927
  chunkType,
5831
5928
  name: chunk.name ?? void 0,
5832
5929
  language: chunk.language,
5833
- hash: chunk.contentHash
5930
+ hash: chunk.contentHash,
5931
+ ...metadataFromBlame(blameFromChunkData(chunk))
5834
5932
  }
5835
5933
  });
5836
5934
  }
@@ -5999,6 +6097,21 @@ function matchesSearchFilters(candidate, options, minScore) {
5999
6097
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
6000
6098
  return false;
6001
6099
  }
6100
+ if (options?.blameAuthor) {
6101
+ const author = options.blameAuthor.toLowerCase();
6102
+ const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
6103
+ const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
6104
+ if (candidateAuthor !== author && candidateEmail !== author) return false;
6105
+ }
6106
+ if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
6107
+ return false;
6108
+ }
6109
+ if (options?.blameSince) {
6110
+ const sinceMs = Date.parse(options.blameSince);
6111
+ if (Number.isNaN(sinceMs)) return false;
6112
+ const committedAt = candidate.metadata.blameCommittedAt;
6113
+ if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
6114
+ }
6002
6115
  return true;
6003
6116
  }
6004
6117
  function unionCandidates(semanticCandidates, keywordCandidates) {
@@ -6037,14 +6150,14 @@ var Indexer = class {
6037
6150
  querySimilarityThreshold = 0.85;
6038
6151
  indexCompatibility = null;
6039
6152
  indexingLockPath = "";
6040
- constructor(projectRoot2, config, host = "opencode") {
6041
- this.projectRoot = projectRoot2;
6153
+ constructor(projectRoot3, config, host = "opencode") {
6154
+ this.projectRoot = projectRoot3;
6042
6155
  this.config = config;
6043
6156
  this.host = host;
6044
6157
  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");
6158
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6159
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6160
+ this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
6048
6161
  this.logger = initializeLogger(config.debug);
6049
6162
  }
6050
6163
  getIndexPath() {
@@ -6076,14 +6189,14 @@ var Indexer = class {
6076
6189
  }
6077
6190
  atomicWriteSync(targetPath, data) {
6078
6191
  const tempPath = `${targetPath}.tmp`;
6079
- mkdirSync2(path11.dirname(targetPath), { recursive: true });
6192
+ mkdirSync2(path12.dirname(targetPath), { recursive: true });
6080
6193
  writeFileSync2(tempPath, data);
6081
6194
  renameSync(tempPath, targetPath);
6082
6195
  }
6083
6196
  getScopedRoots() {
6084
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6197
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
6085
6198
  for (const kbRoot of this.config.knowledgeBases) {
6086
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6199
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
6087
6200
  }
6088
6201
  return Array.from(roots);
6089
6202
  }
@@ -6092,29 +6205,29 @@ var Indexer = class {
6092
6205
  if (this.config.scope !== "global") {
6093
6206
  return branchName;
6094
6207
  }
6095
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6208
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6096
6209
  return `${projectHash}:${branchName}`;
6097
6210
  }
6098
6211
  getBranchCatalogKeyFor(branchName) {
6099
6212
  if (this.config.scope !== "global") {
6100
6213
  return branchName;
6101
6214
  }
6102
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6215
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6103
6216
  return `${projectHash}:${branchName}`;
6104
6217
  }
6105
6218
  getLegacyBranchCatalogKey() {
6106
6219
  return this.currentBranch || "default";
6107
6220
  }
6108
6221
  getLegacyMigrationMetadataKey() {
6109
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6222
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6110
6223
  return `index.globalBranchMigration.${projectHash}`;
6111
6224
  }
6112
6225
  getProjectEmbeddingStrategyMetadataKey() {
6113
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6226
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6114
6227
  return `index.embeddingStrategyVersion.${projectHash}`;
6115
6228
  }
6116
6229
  getProjectForceReembedMetadataKey() {
6117
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6230
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6118
6231
  return `index.forceReembed.${projectHash}`;
6119
6232
  }
6120
6233
  hasProjectForceReembedPending() {
@@ -6208,7 +6321,7 @@ var Indexer = class {
6208
6321
  if (!this.database) {
6209
6322
  return { chunkIds, symbolIds };
6210
6323
  }
6211
- const projectRootPath = path11.resolve(this.projectRoot);
6324
+ const projectRootPath = path12.resolve(this.projectRoot);
6212
6325
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6213
6326
  ...Array.from(this.fileHashCache.keys()).filter(
6214
6327
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6231,7 +6344,7 @@ var Indexer = class {
6231
6344
  if (this.config.scope !== "global") {
6232
6345
  return this.getBranchCatalogCleanupKeys();
6233
6346
  }
6234
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6347
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6235
6348
  const keys = /* @__PURE__ */ new Set();
6236
6349
  const projectChunkIdSet = new Set(projectChunkIds);
6237
6350
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6315,7 +6428,7 @@ var Indexer = class {
6315
6428
  if (!this.database || this.config.scope !== "global") {
6316
6429
  return false;
6317
6430
  }
6318
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6431
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6319
6432
  const roots = this.getScopedRoots();
6320
6433
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6321
6434
  return this.database.getAllBranches().some(
@@ -6349,7 +6462,7 @@ var Indexer = class {
6349
6462
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6350
6463
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6351
6464
  ]);
6352
- const projectRootPath = path11.resolve(this.projectRoot);
6465
+ const projectRootPath = path12.resolve(this.projectRoot);
6353
6466
  const projectLocalFilePaths = new Set(
6354
6467
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6355
6468
  );
@@ -6709,13 +6822,13 @@ var Indexer = class {
6709
6822
  }
6710
6823
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
6711
6824
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6712
- const storePath = path11.join(this.indexPath, "vectors");
6825
+ const storePath = path12.join(this.indexPath, "vectors");
6713
6826
  this.store = new VectorStore(storePath, dimensions);
6714
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6827
+ const indexFilePath = path12.join(this.indexPath, "vectors.usearch");
6715
6828
  if (existsSync7(indexFilePath)) {
6716
6829
  this.store.load();
6717
6830
  }
6718
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6831
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
6719
6832
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6720
6833
  try {
6721
6834
  this.invertedIndex.load();
@@ -6725,7 +6838,7 @@ var Indexer = class {
6725
6838
  }
6726
6839
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6727
6840
  }
6728
- const dbPath = path11.join(this.indexPath, "codebase.db");
6841
+ const dbPath = path12.join(this.indexPath, "codebase.db");
6729
6842
  let dbIsNew = !existsSync7(dbPath);
6730
6843
  try {
6731
6844
  this.database = new Database(dbPath);
@@ -6806,7 +6919,7 @@ var Indexer = class {
6806
6919
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
6807
6920
  return {
6808
6921
  resetCorruptedIndex: true,
6809
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
6922
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
6810
6923
  };
6811
6924
  }
6812
6925
  throw error;
@@ -6821,7 +6934,7 @@ var Indexer = class {
6821
6934
  return;
6822
6935
  }
6823
6936
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
6824
- const storeBasePath = path11.join(this.indexPath, "vectors");
6937
+ const storeBasePath = path12.join(this.indexPath, "vectors");
6825
6938
  const storeIndexPath = `${storeBasePath}.usearch`;
6826
6939
  const storeMetadataPath = `${storeBasePath}.meta.json`;
6827
6940
  const backupIndexPath = `${storeIndexPath}.bak`;
@@ -6906,7 +7019,7 @@ var Indexer = class {
6906
7019
  if (!isSqliteCorruptionError(error)) {
6907
7020
  return false;
6908
7021
  }
6909
- const dbPath = path11.join(this.indexPath, "codebase.db");
7022
+ const dbPath = path12.join(this.indexPath, "codebase.db");
6910
7023
  const warning = this.getCorruptedIndexWarning(dbPath);
6911
7024
  const errorMessage = getErrorMessage2(error);
6912
7025
  if (this.config.scope === "global") {
@@ -6929,15 +7042,15 @@ var Indexer = class {
6929
7042
  this.indexCompatibility = null;
6930
7043
  this.fileHashCache.clear();
6931
7044
  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")
7045
+ path12.join(this.indexPath, "codebase.db"),
7046
+ path12.join(this.indexPath, "codebase.db-shm"),
7047
+ path12.join(this.indexPath, "codebase.db-wal"),
7048
+ path12.join(this.indexPath, "vectors.usearch"),
7049
+ path12.join(this.indexPath, "inverted-index.json"),
7050
+ path12.join(this.indexPath, "file-hashes.json"),
7051
+ path12.join(this.indexPath, "failed-batches.json"),
7052
+ path12.join(this.indexPath, "indexing.lock"),
7053
+ path12.join(this.indexPath, "vectors")
6941
7054
  ];
6942
7055
  await Promise.all(resetPaths.map(async (targetPath) => {
6943
7056
  try {
@@ -7174,6 +7287,7 @@ var Indexer = class {
7174
7287
  this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
7175
7288
  const existingChunks = /* @__PURE__ */ new Map();
7176
7289
  const existingChunksByFile = /* @__PURE__ */ new Map();
7290
+ const existingMetadataById = /* @__PURE__ */ new Map();
7177
7291
  for (const { key, metadata } of store.getAllMetadata()) {
7178
7292
  if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
7179
7293
  continue;
@@ -7182,6 +7296,7 @@ var Indexer = class {
7182
7296
  continue;
7183
7297
  }
7184
7298
  existingChunks.set(key, metadata.hash);
7299
+ existingMetadataById.set(key, metadata);
7185
7300
  const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
7186
7301
  fileChunks.add(key);
7187
7302
  existingChunksByFile.set(metadata.filePath, fileChunks);
@@ -7189,6 +7304,8 @@ var Indexer = class {
7189
7304
  const currentChunkIds = /* @__PURE__ */ new Set();
7190
7305
  const currentFilePaths = /* @__PURE__ */ new Set();
7191
7306
  const pendingChunks = [];
7307
+ const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
7308
+ let backfilledBlameMetadata = false;
7192
7309
  for (const filePath of unchangedFilePaths) {
7193
7310
  currentFilePaths.add(filePath);
7194
7311
  const fileChunks = existingChunksByFile.get(filePath);
@@ -7199,10 +7316,52 @@ var Indexer = class {
7199
7316
  }
7200
7317
  }
7201
7318
  const chunkDataBatch = [];
7319
+ if (gitBlameEnabled) {
7320
+ const backfillItems = [];
7321
+ for (const chunkId of currentChunkIds) {
7322
+ const metadata = existingMetadataById.get(chunkId);
7323
+ if (!metadata || hasBlameMetadata(metadata)) {
7324
+ continue;
7325
+ }
7326
+ const chunk = database.getChunk(chunkId);
7327
+ if (!chunk) {
7328
+ continue;
7329
+ }
7330
+ const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
7331
+ const blameMetadata = metadataFromBlame(blame);
7332
+ if (!blameMetadata.blameSha) {
7333
+ continue;
7334
+ }
7335
+ chunkDataBatch.push({
7336
+ ...chunk,
7337
+ blameSha: blameMetadata.blameSha,
7338
+ blameAuthor: blameMetadata.blameAuthor,
7339
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
7340
+ blameCommittedAt: blameMetadata.blameCommittedAt,
7341
+ blameSummary: blameMetadata.blameSummary
7342
+ });
7343
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
7344
+ if (!embeddingBuffer) {
7345
+ continue;
7346
+ }
7347
+ backfillItems.push({
7348
+ id: chunkId,
7349
+ vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
7350
+ metadata: {
7351
+ ...metadata,
7352
+ ...blameMetadata
7353
+ }
7354
+ });
7355
+ }
7356
+ if (backfillItems.length > 0) {
7357
+ store.addBatch(backfillItems);
7358
+ backfilledBlameMetadata = true;
7359
+ }
7360
+ }
7202
7361
  for (const parsed of parsedFiles) {
7203
7362
  currentFilePaths.add(parsed.path);
7204
7363
  if (parsed.chunks.length === 0) {
7205
- const relativePath = path11.relative(this.projectRoot, parsed.path);
7364
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
7206
7365
  stats.parseFailures.push(relativePath);
7207
7366
  }
7208
7367
  let fileChunkCount = 0;
@@ -7223,6 +7382,10 @@ var Indexer = class {
7223
7382
  }
7224
7383
  const id = generateChunkId(parsed.path, chunk);
7225
7384
  const contentHash = generateChunkHash(chunk);
7385
+ const existingContentHash = existingChunks.get(id);
7386
+ const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
7387
+ const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
7388
+ const blameMetadata = metadataFromBlame(blame);
7226
7389
  currentChunkIds.add(id);
7227
7390
  chunkDataBatch.push({
7228
7391
  chunkId: id,
@@ -7232,15 +7395,20 @@ var Indexer = class {
7232
7395
  endLine: chunk.endLine,
7233
7396
  nodeType: chunk.chunkType,
7234
7397
  name: chunk.name,
7235
- language: chunk.language
7398
+ language: chunk.language,
7399
+ blameSha: blameMetadata.blameSha,
7400
+ blameAuthor: blameMetadata.blameAuthor,
7401
+ blameAuthorEmail: blameMetadata.blameAuthorEmail,
7402
+ blameCommittedAt: blameMetadata.blameCommittedAt,
7403
+ blameSummary: blameMetadata.blameSummary
7236
7404
  });
7237
- if (existingChunks.get(id) === contentHash) {
7405
+ if (existingContentHash === contentHash) {
7238
7406
  fileChunkCount++;
7239
7407
  continue;
7240
7408
  }
7241
- const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text2) => ({
7242
- text: text2,
7243
- tokenCount: estimateTokens(text2)
7409
+ const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
7410
+ text: text3,
7411
+ tokenCount: estimateTokens(text3)
7244
7412
  }));
7245
7413
  const metadata = {
7246
7414
  filePath: parsed.path,
@@ -7249,7 +7417,8 @@ var Indexer = class {
7249
7417
  chunkType: chunk.chunkType,
7250
7418
  name: chunk.name,
7251
7419
  language: chunk.language,
7252
- hash: contentHash
7420
+ hash: contentHash,
7421
+ ...blameMetadata
7253
7422
  };
7254
7423
  pendingChunks.push({
7255
7424
  id,
@@ -7392,6 +7561,9 @@ var Indexer = class {
7392
7561
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7393
7562
  database.clearBranchSymbols(branchCatalogKey);
7394
7563
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7564
+ if (backfilledBlameMetadata) {
7565
+ store.save();
7566
+ }
7395
7567
  if (scopedRoots) {
7396
7568
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7397
7569
  this.clearScopedFailedBatches(scopedRoots);
@@ -7771,9 +7943,9 @@ var Indexer = class {
7771
7943
  }
7772
7944
  return bestMatch;
7773
7945
  }
7774
- tokenize(text2) {
7946
+ tokenize(text3) {
7775
7947
  return new Set(
7776
- text2.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
7948
+ text3.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
7777
7949
  );
7778
7950
  }
7779
7951
  jaccardSimilarity(a, b) {
@@ -7955,7 +8127,8 @@ var Indexer = class {
7955
8127
  content,
7956
8128
  score: r.score,
7957
8129
  chunkType: r.metadata.chunkType,
7958
- name: r.metadata.name
8130
+ name: r.metadata.name,
8131
+ blame: blameFromMetadata(r.metadata)
7959
8132
  };
7960
8133
  })
7961
8134
  );
@@ -8047,12 +8220,12 @@ var Indexer = class {
8047
8220
  this.indexCompatibility = compatibility;
8048
8221
  return;
8049
8222
  }
8050
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8223
+ const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8051
8224
  if (this.host !== "opencode") {
8052
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8225
+ localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8053
8226
  }
8054
8227
  const isLocalProjectIndex = localProjectIndexPaths.some(
8055
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8228
+ (localPath) => path12.resolve(this.indexPath) === path12.resolve(localPath)
8056
8229
  );
8057
8230
  if (!isLocalProjectIndex) {
8058
8231
  throw new Error(
@@ -8142,7 +8315,7 @@ var Indexer = class {
8142
8315
  gcOrphanSymbols: 0,
8143
8316
  gcOrphanCallEdges: 0,
8144
8317
  resetCorruptedIndex: true,
8145
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
8318
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8146
8319
  };
8147
8320
  }
8148
8321
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8462,7 +8635,8 @@ var Indexer = class {
8462
8635
  content,
8463
8636
  score: r.score,
8464
8637
  chunkType: r.metadata.chunkType,
8465
- name: r.metadata.name
8638
+ name: r.metadata.name,
8639
+ blame: blameFromMetadata(r.metadata)
8466
8640
  };
8467
8641
  })
8468
8642
  );
@@ -8499,9 +8673,9 @@ var Indexer = class {
8499
8673
  const { database } = await this.ensureInitialized();
8500
8674
  let shortest = [];
8501
8675
  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;
8676
+ const path16 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8677
+ if (path16.length > 0 && (shortest.length === 0 || path16.length < shortest.length)) {
8678
+ shortest = path16;
8505
8679
  }
8506
8680
  }
8507
8681
  return shortest;
@@ -8533,7 +8707,7 @@ var Indexer = class {
8533
8707
  }
8534
8708
  async getPrImpact(opts) {
8535
8709
  const { database } = await this.ensureInitialized();
8536
- const execFileAsync2 = promisify2(execFile2);
8710
+ const execFileAsync3 = promisify3(execFile3);
8537
8711
  const changedFilesResult = await getChangedFiles({
8538
8712
  pr: opts.pr,
8539
8713
  branch: opts.branch,
@@ -8555,7 +8729,7 @@ var Indexer = class {
8555
8729
  "Run index_codebase first to build the call graph and symbol index for this branch."
8556
8730
  );
8557
8731
  }
8558
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
8732
+ const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
8559
8733
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8560
8734
  const directIds = directSymbols.map((s) => s.id);
8561
8735
  const direction = opts.direction ?? "both";
@@ -8617,7 +8791,7 @@ var Indexer = class {
8617
8791
  if (opts.checkConflicts) {
8618
8792
  conflictingPRs = [];
8619
8793
  try {
8620
- const { stdout } = await execFileAsync2(
8794
+ const { stdout } = await execFileAsync3(
8621
8795
  "gh",
8622
8796
  ["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
8623
8797
  { cwd: this.projectRoot, timeout: 3e4 }
@@ -8638,7 +8812,7 @@ var Indexer = class {
8638
8812
  projectRoot: this.projectRoot,
8639
8813
  baseBranch: this.baseBranch
8640
8814
  });
8641
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
8815
+ const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
8642
8816
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8643
8817
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8644
8818
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8701,12 +8875,12 @@ var Indexer = class {
8701
8875
  if (meta.filePath) filePaths.add(meta.filePath);
8702
8876
  }
8703
8877
  const directory = options?.directory?.replace(/\/$/, "");
8704
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
8878
+ const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
8705
8879
  for (const filePath of filePaths) {
8706
8880
  if (directory) {
8707
- const absoluteFilePath = path11.resolve(filePath);
8881
+ const absoluteFilePath = path12.resolve(filePath);
8708
8882
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8709
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
8883
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
8710
8884
  if (!matchesRelative && !matchesProjectRelative) {
8711
8885
  continue;
8712
8886
  }
@@ -8738,43 +8912,43 @@ var Indexer = class {
8738
8912
  };
8739
8913
 
8740
8914
  // src/tools/knowledge-base-paths.ts
8741
- import * as path12 from "path";
8915
+ import * as path13 from "path";
8742
8916
  function resolveConfigPathValue(value, baseDir) {
8743
8917
  const trimmed = value.trim();
8744
8918
  if (!trimmed) {
8745
8919
  return trimmed;
8746
8920
  }
8747
- const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
8748
- return path12.normalize(absolutePath);
8921
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
8922
+ return path13.normalize(absolutePath);
8749
8923
  }
8750
8924
  function serializeConfigPathValue(value, baseDir) {
8751
8925
  const trimmed = value.trim();
8752
8926
  if (!trimmed) {
8753
8927
  return trimmed;
8754
8928
  }
8755
- if (!path12.isAbsolute(trimmed)) {
8756
- return normalizePathSeparators(path12.normalize(trimmed));
8929
+ if (!path13.isAbsolute(trimmed)) {
8930
+ return normalizePathSeparators(path13.normalize(trimmed));
8757
8931
  }
8758
- const relativePath = path12.relative(baseDir, trimmed);
8759
- if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
8760
- return normalizePathSeparators(path12.normalize(relativePath || "."));
8932
+ const relativePath = path13.relative(baseDir, trimmed);
8933
+ if (!relativePath || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath)) {
8934
+ return normalizePathSeparators(path13.normalize(relativePath || "."));
8761
8935
  }
8762
- return path12.normalize(trimmed);
8936
+ return path13.normalize(trimmed);
8763
8937
  }
8764
- function resolveKnowledgeBasePath(value, projectRoot2) {
8765
- return path12.isAbsolute(value) ? value : path12.resolve(projectRoot2, value);
8938
+ function resolveKnowledgeBasePath(value, projectRoot3) {
8939
+ return path13.isAbsolute(value) ? value : path13.resolve(projectRoot3, value);
8766
8940
  }
8767
- function normalizeKnowledgeBasePath2(value, projectRoot2) {
8768
- return path12.normalize(resolveKnowledgeBasePath(value, projectRoot2));
8941
+ function normalizeKnowledgeBasePath2(value, projectRoot3) {
8942
+ return path13.normalize(resolveKnowledgeBasePath(value, projectRoot3));
8769
8943
  }
8770
- function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot2) {
8771
- const normalizedInput = path12.normalize(inputPath);
8772
- return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput);
8944
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
8945
+ const normalizedInput = path13.normalize(inputPath);
8946
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
8773
8947
  }
8774
- function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot2) {
8775
- const normalizedInput = path12.normalize(inputPath);
8948
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
8949
+ const normalizedInput = path13.normalize(inputPath);
8776
8950
  return knowledgeBases.findIndex(
8777
- (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot2) === normalizedInput
8951
+ (kb) => path13.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
8778
8952
  );
8779
8953
  }
8780
8954
 
@@ -8947,16 +9121,57 @@ function formatHealthCheck(result) {
8947
9121
  }
8948
9122
  return lines.join("\n");
8949
9123
  }
9124
+ function formatCallGraphCallers(name, callers, relationshipType) {
9125
+ if (callers.length === 0) {
9126
+ return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
9127
+ }
9128
+ const formatted = callers.map((edge, index) => {
9129
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
9130
+ 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]"}`;
9131
+ });
9132
+ return `"${name}" is called by ${callers.length} function(s):
9133
+
9134
+ ${formatted.join("\n")}`;
9135
+ }
9136
+ function formatCallGraphCallees(symbolId, callees, relationshipType) {
9137
+ if (callees.length === 0) {
9138
+ return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
9139
+ }
9140
+ return callees.map((edge, index) => {
9141
+ const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
9142
+ return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
9143
+ }).join("\n");
9144
+ }
9145
+ function formatCallGraphPath(from, to, path16) {
9146
+ if (path16.length === 0) {
9147
+ return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
9148
+ }
9149
+ const formatted = path16.map((hop, index) => {
9150
+ const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
9151
+ const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
9152
+ return `${prefix} ${hop.symbolName}${location}`;
9153
+ });
9154
+ return `Path (${path16.length} hops):
9155
+ ${formatted.join("\n")}`;
9156
+ }
8950
9157
  function formatResultHeader(result, index) {
8951
9158
  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
9159
  }
9160
+ function formatBlame(result) {
9161
+ if (!result.blame) {
9162
+ return "";
9163
+ }
9164
+ const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
9165
+ return `
9166
+ ${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
9167
+ }
8953
9168
  function formatDefinitionLookup(results, query) {
8954
9169
  if (results.length === 0) {
8955
9170
  return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
8956
9171
  }
8957
9172
  const formatted = results.map((r, idx) => {
8958
9173
  const header = formatResultHeader(r, idx);
8959
- return `${header} (score: ${r.score.toFixed(2)})
9174
+ return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
8960
9175
  \`\`\`
8961
9176
  ${truncateContent(r.content)}
8962
9177
  \`\`\``;
@@ -8967,7 +9182,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
8967
9182
  const formatted = results.map((r, idx) => {
8968
9183
  const header = formatResultHeader(r, idx);
8969
9184
  const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
8970
- return `${header} ${scoreLabel}
9185
+ return `${header} ${scoreLabel}${formatBlame(r)}
8971
9186
  \`\`\`
8972
9187
  ${truncateContent(r.content)}
8973
9188
  \`\`\``;
@@ -8977,12 +9192,12 @@ ${truncateContent(r.content)}
8977
9192
 
8978
9193
  // src/tools/config-state.ts
8979
9194
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
8980
- import * as path13 from "path";
8981
- function normalizeKnowledgeBasePaths(config, projectRoot2) {
9195
+ import * as path14 from "path";
9196
+ function normalizeKnowledgeBasePaths(config, projectRoot3) {
8982
9197
  const normalized = { ...config };
8983
9198
  if (Array.isArray(normalized.knowledgeBases)) {
8984
9199
  normalized.knowledgeBases = normalized.knowledgeBases.map(
8985
- (kb) => resolveConfigPathValue(kb, projectRoot2)
9200
+ (kb) => resolveConfigPathValue(kb, projectRoot3)
8986
9201
  );
8987
9202
  }
8988
9203
  return normalized;
@@ -8993,19 +9208,19 @@ function toConfigRecord(rawConfig) {
8993
9208
  }
8994
9209
  return { ...rawConfig };
8995
9210
  }
8996
- function getConfigPath(projectRoot2, host = "opencode") {
8997
- return resolveWritableProjectConfigPath(projectRoot2, host);
9211
+ function getConfigPath(projectRoot3, host = "opencode") {
9212
+ return resolveWritableProjectConfigPath(projectRoot3, host);
8998
9213
  }
8999
- function loadRuntimeConfig(projectRoot2, host = "opencode") {
9000
- return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot2, host)), projectRoot2);
9214
+ function loadRuntimeConfig(projectRoot3, host = "opencode") {
9215
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot3, host)), projectRoot3);
9001
9216
  }
9002
- function loadEditableConfig(projectRoot2, host = "opencode") {
9003
- return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot2, host)), projectRoot2);
9217
+ function loadEditableConfig(projectRoot3, host = "opencode") {
9218
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot3, host)), projectRoot3);
9004
9219
  }
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);
9220
+ function saveConfig(projectRoot3, config, host = "opencode") {
9221
+ const configPath = getConfigPath(projectRoot3, host);
9222
+ const configDir = path14.dirname(configPath);
9223
+ const configBaseDir = path14.dirname(configDir);
9009
9224
  if (!existsSync8(configDir)) {
9010
9225
  mkdirSync3(configDir, { recursive: true });
9011
9226
  }
@@ -9022,9 +9237,9 @@ function saveConfig(projectRoot2, config, host = "opencode") {
9022
9237
  var indexerCache = /* @__PURE__ */ new Map();
9023
9238
  var configCache = /* @__PURE__ */ new Map();
9024
9239
  var defaultProjectRoots = /* @__PURE__ */ new Map();
9025
- function getProjectRoot(projectRoot2, host) {
9026
- if (projectRoot2) {
9027
- return projectRoot2;
9240
+ function getProjectRoot(projectRoot3, host) {
9241
+ if (projectRoot3) {
9242
+ return projectRoot3;
9028
9243
  }
9029
9244
  const root = defaultProjectRoots.get(host);
9030
9245
  if (!root) {
@@ -9032,53 +9247,56 @@ function getProjectRoot(projectRoot2, host) {
9032
9247
  }
9033
9248
  return root;
9034
9249
  }
9035
- function getIndexerCacheKey(projectRoot2, host) {
9036
- return `${host}::${projectRoot2}`;
9250
+ function getIndexerCacheKey(projectRoot3, host) {
9251
+ return `${host}::${projectRoot3}`;
9037
9252
  }
9038
- function getOrCreateIndexer(projectRoot2, host) {
9039
- const key = getIndexerCacheKey(projectRoot2, host);
9253
+ function getOrCreateIndexer(projectRoot3, host) {
9254
+ const key = getIndexerCacheKey(projectRoot3, host);
9040
9255
  const cached = indexerCache.get(key);
9041
9256
  if (cached) {
9042
9257
  return cached;
9043
9258
  }
9044
- const config = parseConfig(loadRuntimeConfig(projectRoot2, host));
9045
- const indexer = new Indexer(projectRoot2, config, host);
9259
+ const config = parseConfig(loadRuntimeConfig(projectRoot3, host));
9260
+ const indexer = new Indexer(projectRoot3, config, host);
9046
9261
  indexerCache.set(key, indexer);
9047
9262
  configCache.set(key, config);
9048
9263
  return indexer;
9049
9264
  }
9050
- function getIndexerForProject(projectRoot2, host = "opencode") {
9051
- const root = getProjectRoot(projectRoot2, host);
9265
+ function getIndexerForProject(projectRoot3, host = "opencode") {
9266
+ const root = getProjectRoot(projectRoot3, host);
9052
9267
  return getOrCreateIndexer(root, host);
9053
9268
  }
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);
9269
+ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot3, host))) {
9270
+ const key = getIndexerCacheKey(projectRoot3, host);
9271
+ const indexer = new Indexer(projectRoot3, config, host);
9057
9272
  indexerCache.set(key, indexer);
9058
9273
  configCache.set(key, config);
9059
9274
  }
9060
- function shouldForceLocalizeProjectIndex(projectRoot2, host = "opencode") {
9061
- const root = getProjectRoot(projectRoot2, host);
9062
- const localIndexPath = path14.join(root, getHostProjectIndexRelativePath(host));
9275
+ function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
9276
+ const root = getProjectRoot(projectRoot3, host);
9277
+ const localIndexPath = path15.join(root, getHostProjectIndexRelativePath(host));
9063
9278
  if (existsSync9(localIndexPath)) {
9064
9279
  return false;
9065
9280
  }
9066
9281
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
9067
9282
  return inheritedIndexPath !== null;
9068
9283
  }
9069
- async function searchCodebase(projectRoot2, host, query, options = {}) {
9070
- const indexer = getIndexerForProject(projectRoot2, host);
9284
+ async function searchCodebase(projectRoot3, host, query, options = {}) {
9285
+ const indexer = getIndexerForProject(projectRoot3, host);
9071
9286
  return indexer.search(query, options.limit, {
9072
9287
  fileType: options.fileType,
9073
9288
  directory: options.directory,
9074
9289
  chunkType: options.chunkType,
9075
9290
  contextLines: options.contextLines,
9076
9291
  metadataOnly: options.metadataOnly,
9077
- definitionIntent: options.definitionIntent
9292
+ definitionIntent: options.definitionIntent,
9293
+ blameAuthor: options.blameAuthor,
9294
+ blameSha: options.blameSha,
9295
+ blameSince: options.blameSince
9078
9296
  });
9079
9297
  }
9080
- async function findSimilarCode(projectRoot2, host, code, options = {}) {
9081
- const indexer = getIndexerForProject(projectRoot2, host);
9298
+ async function findSimilarCode(projectRoot3, host, code, options = {}) {
9299
+ const indexer = getIndexerForProject(projectRoot3, host);
9082
9300
  return indexer.findSimilar(code, options.limit, {
9083
9301
  fileType: options.fileType,
9084
9302
  directory: options.directory,
@@ -9086,16 +9304,16 @@ async function findSimilarCode(projectRoot2, host, code, options = {}) {
9086
9304
  excludeFile: options.excludeFile
9087
9305
  });
9088
9306
  }
9089
- async function implementationLookup(projectRoot2, host, query, options = {}) {
9090
- const indexer = getIndexerForProject(projectRoot2, host);
9307
+ async function implementationLookup(projectRoot3, host, query, options = {}) {
9308
+ const indexer = getIndexerForProject(projectRoot3, host);
9091
9309
  return indexer.search(query, options.limit, {
9092
9310
  fileType: options.fileType,
9093
9311
  directory: options.directory,
9094
9312
  definitionIntent: true
9095
9313
  });
9096
9314
  }
9097
- async function getCallGraphData(projectRoot2, host, params) {
9098
- const indexer = getIndexerForProject(projectRoot2, host);
9315
+ async function getCallGraphData(projectRoot3, host, params) {
9316
+ const indexer = getIndexerForProject(projectRoot3, host);
9099
9317
  if (params.direction === "callees") {
9100
9318
  if (!params.symbolId) {
9101
9319
  return { direction: "callees", callees: [], callers: [] };
@@ -9106,12 +9324,12 @@ async function getCallGraphData(projectRoot2, host, params) {
9106
9324
  const callers = await indexer.getCallers(params.name, params.relationshipType);
9107
9325
  return { direction: "callers", callers, callees: [] };
9108
9326
  }
9109
- async function getCallGraphPath(projectRoot2, host, from, to, maxDepth) {
9110
- const indexer = getIndexerForProject(projectRoot2, host);
9327
+ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
9328
+ const indexer = getIndexerForProject(projectRoot3, host);
9111
9329
  return indexer.findCallPath(from, to, maxDepth);
9112
9330
  }
9113
- async function runIndexCodebase(projectRoot2, host, args, onProgress) {
9114
- const root = getProjectRoot(projectRoot2, host);
9331
+ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
9332
+ const root = getProjectRoot(projectRoot3, host);
9115
9333
  const key = getIndexerCacheKey(root, host);
9116
9334
  const cachedConfig = configCache.get(key);
9117
9335
  let indexer = getIndexerForProject(root, host);
@@ -9143,16 +9361,16 @@ async function runIndexCodebase(projectRoot2, host, args, onProgress) {
9143
9361
  });
9144
9362
  return { kind: "stats", stats };
9145
9363
  }
9146
- async function getIndexStatus(projectRoot2, host) {
9147
- const indexer = getIndexerForProject(projectRoot2, host);
9364
+ async function getIndexStatus(projectRoot3, host) {
9365
+ const indexer = getIndexerForProject(projectRoot3, host);
9148
9366
  return indexer.getStatus();
9149
9367
  }
9150
- async function getIndexHealthCheck(projectRoot2, host) {
9151
- const indexer = getIndexerForProject(projectRoot2, host);
9368
+ async function getIndexHealthCheck(projectRoot3, host) {
9369
+ const indexer = getIndexerForProject(projectRoot3, host);
9152
9370
  return indexer.healthCheck();
9153
9371
  }
9154
- async function getPrImpact(projectRoot2, host, params) {
9155
- const indexer = getIndexerForProject(projectRoot2, host);
9372
+ async function getPrImpact(projectRoot3, host, params) {
9373
+ const indexer = getIndexerForProject(projectRoot3, host);
9156
9374
  return indexer.getPrImpact({
9157
9375
  pr: params.pr,
9158
9376
  branch: params.branch,
@@ -9162,8 +9380,8 @@ async function getPrImpact(projectRoot2, host, params) {
9162
9380
  direction: params.direction
9163
9381
  });
9164
9382
  }
9165
- async function getIndexMetrics(projectRoot2, host) {
9166
- const indexer = getIndexerForProject(projectRoot2, host);
9383
+ async function getIndexMetrics(projectRoot3, host) {
9384
+ const indexer = getIndexerForProject(projectRoot3, host);
9167
9385
  const logger = indexer.getLogger();
9168
9386
  if (!logger.isEnabled()) {
9169
9387
  return {
@@ -9185,8 +9403,8 @@ async function getIndexMetrics(projectRoot2, host) {
9185
9403
  text: logger.formatMetrics()
9186
9404
  };
9187
9405
  }
9188
- async function getIndexLogs(projectRoot2, host, args) {
9189
- const indexer = getIndexerForProject(projectRoot2, host);
9406
+ async function getIndexLogs(projectRoot3, host, args) {
9407
+ const indexer = getIndexerForProject(projectRoot3, host);
9190
9408
  const logger = indexer.getLogger();
9191
9409
  if (!logger.isEnabled()) {
9192
9410
  return {
@@ -9208,17 +9426,17 @@ async function getIndexLogs(projectRoot2, host, args) {
9208
9426
  text: "No logs recorded yet. Logs are captured during indexing and search operations."
9209
9427
  };
9210
9428
  }
9211
- const text2 = logs.map((entry) => {
9429
+ const text3 = logs.map((entry) => {
9212
9430
  const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
9213
9431
  return `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.category}] ${entry.message}${dataStr}`;
9214
9432
  }).join("\n");
9215
- return { kind: "entries", text: text2 };
9433
+ return { kind: "entries", text: text3 };
9216
9434
  }
9217
- function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9218
- const root = getProjectRoot(projectRoot2, host);
9435
+ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
9436
+ const root = getProjectRoot(projectRoot3, host);
9219
9437
  const inputPath = knowledgeBasePath.trim();
9220
- const normalizedPath = path14.resolve(
9221
- path14.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9438
+ const normalizedPath = path15.resolve(
9439
+ path15.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
9222
9440
  );
9223
9441
  if (!existsSync9(normalizedPath)) {
9224
9442
  return `Error: Directory does not exist: ${normalizedPath}`;
@@ -9254,7 +9472,7 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9254
9472
  }
9255
9473
  }
9256
9474
  for (const dotDir of sensitiveDotDirs) {
9257
- const sensitiveDir = path14.join(homeDir, dotDir);
9475
+ const sensitiveDir = path15.join(homeDir, dotDir);
9258
9476
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
9259
9477
  return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
9260
9478
  }
@@ -9287,8 +9505,8 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9287
9505
  Run /index to rebuild the index with the new knowledge base.`;
9288
9506
  return result;
9289
9507
  }
9290
- function listKnowledgeBases(projectRoot2, host) {
9291
- const root = getProjectRoot(projectRoot2, host);
9508
+ function listKnowledgeBases(projectRoot3, host) {
9509
+ const root = getProjectRoot(projectRoot3, host);
9292
9510
  const config = loadRuntimeConfig(root, host);
9293
9511
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9294
9512
  if (knowledgeBases.length === 0) {
@@ -9317,7 +9535,7 @@ function listKnowledgeBases(projectRoot2, host) {
9317
9535
  }
9318
9536
  result += "\n";
9319
9537
  }
9320
- const hasHostConfig = existsSync9(path14.join(root, getHostProjectConfigRelativePath(host)));
9538
+ const hasHostConfig = existsSync9(path15.join(root, getHostProjectConfigRelativePath(host)));
9321
9539
  if (hasHostConfig) {
9322
9540
  result += `
9323
9541
  Config sources: 1 file(s).`;
@@ -9326,8 +9544,8 @@ Config sources: 1 file(s).`;
9326
9544
  Config file: ${getConfigPath(root, host)}`;
9327
9545
  return result;
9328
9546
  }
9329
- function removeKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
9330
- const root = getProjectRoot(projectRoot2, host);
9547
+ function removeKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
9548
+ const root = getProjectRoot(projectRoot3, host);
9331
9549
  const config = loadEditableConfig(root, host);
9332
9550
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
9333
9551
  const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
@@ -9350,17 +9568,9 @@ Run /index to rebuild the index without the removed knowledge base.`;
9350
9568
  return result;
9351
9569
  }
9352
9570
 
9353
- // src/pi-extension.ts
9571
+ // src/pi-call-graph.ts
9572
+ import { Type } from "typebox";
9354
9573
  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
9574
  var RelationshipType = Type.Union([
9365
9575
  Type.Literal("Call"),
9366
9576
  Type.Literal("MethodCall"),
@@ -9369,214 +9579,247 @@ var RelationshipType = Type.Union([
9369
9579
  Type.Literal("Inherits"),
9370
9580
  Type.Literal("Implements")
9371
9581
  ]);
9372
- function text(text2, details) {
9373
- return { content: [{ type: "text", text: text2 }], details };
9582
+ function text(text3, details) {
9583
+ return { content: [{ type: "text", text: text3 }], details };
9374
9584
  }
9375
9585
  function projectRoot(ctx) {
9376
9586
  return ctx?.cwd ?? process.cwd();
9377
9587
  }
9588
+ function registerPiCallGraphTools(pi) {
9589
+ pi.registerTool({
9590
+ name: "call_graph",
9591
+ label: "Call Graph",
9592
+ description: "Find callers or callees of a function/method in the indexed call graph.",
9593
+ parameters: Type.Object({
9594
+ name: Type.String(),
9595
+ direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees")])),
9596
+ symbolId: Type.Optional(Type.String()),
9597
+ relationshipType: Type.Optional(RelationshipType)
9598
+ }),
9599
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9600
+ const result = await getCallGraphData(projectRoot(ctx), HOST, params);
9601
+ if (result.direction === "callees") {
9602
+ return text(
9603
+ params.symbolId ? formatCallGraphCallees(params.symbolId, result.callees, params.relationshipType) : "Error: 'symbolId' is required when direction is 'callees'.",
9604
+ result
9605
+ );
9606
+ }
9607
+ return text(formatCallGraphCallers(params.name, result.callers, params.relationshipType), result);
9608
+ }
9609
+ });
9610
+ pi.registerTool({
9611
+ name: "call_graph_path",
9612
+ label: "Call Graph Path",
9613
+ description: "Find a call path between two functions/methods.",
9614
+ parameters: Type.Object({
9615
+ from: Type.String(),
9616
+ to: Type.String(),
9617
+ maxDepth: Type.Optional(Type.Number({ default: 10 }))
9618
+ }),
9619
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9620
+ const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
9621
+ return text(formatCallGraphPath(params.from, params.to, result), result);
9622
+ }
9623
+ });
9624
+ }
9625
+
9626
+ // src/pi-extension.ts
9627
+ var HOST2 = "pi";
9628
+ var ChunkType = Type2.Union([
9629
+ Type2.Literal("function"),
9630
+ Type2.Literal("class"),
9631
+ Type2.Literal("method"),
9632
+ Type2.Literal("interface"),
9633
+ Type2.Literal("type"),
9634
+ Type2.Literal("module"),
9635
+ Type2.Literal("block")
9636
+ ]);
9637
+ function text2(text3, details) {
9638
+ return { content: [{ type: "text", text: text3 }], details };
9639
+ }
9640
+ function projectRoot2(ctx) {
9641
+ return ctx?.cwd ?? process.cwd();
9642
+ }
9378
9643
  function codebaseIndexPiExtension(pi) {
9379
9644
  pi.registerTool({
9380
9645
  name: "codebase_search",
9381
9646
  label: "Codebase Search",
9382
9647
  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" }))
9648
+ parameters: Type2.Object({
9649
+ query: Type2.String({ description: "Natural language description of what code you're looking for" }),
9650
+ limit: Type2.Optional(Type2.Number({ description: "Maximum results (default: 10)" })),
9651
+ fileType: Type2.Optional(Type2.String({ description: "Filter by extension, e.g. ts, py, rs" })),
9652
+ directory: Type2.Optional(Type2.String({ description: "Filter by directory path" })),
9653
+ chunkType: Type2.Optional(ChunkType),
9654
+ contextLines: Type2.Optional(Type2.Number({ description: "Extra lines around each match" })),
9655
+ blameAuthor: Type2.Optional(Type2.String({ description: "Filter by git blame author name or email" })),
9656
+ blameSha: Type2.Optional(Type2.String({ description: "Filter by git blame commit SHA or prefix" })),
9657
+ blameSince: Type2.Optional(Type2.String({ description: "Filter to chunks last changed on or after this date" }))
9390
9658
  }),
9391
9659
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9392
- const results = await searchCodebase(projectRoot(ctx), HOST, params.query, params);
9393
- return text(formatSearchResults(results), results);
9660
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, params);
9661
+ return text2(formatSearchResults(results), results);
9394
9662
  }
9395
9663
  });
9396
9664
  pi.registerTool({
9397
9665
  name: "codebase_peek",
9398
9666
  label: "Codebase Peek",
9399
9667
  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)
9668
+ parameters: Type2.Object({
9669
+ query: Type2.String(),
9670
+ limit: Type2.Optional(Type2.Number()),
9671
+ fileType: Type2.Optional(Type2.String()),
9672
+ directory: Type2.Optional(Type2.String()),
9673
+ chunkType: Type2.Optional(ChunkType),
9674
+ blameAuthor: Type2.Optional(Type2.String()),
9675
+ blameSha: Type2.Optional(Type2.String()),
9676
+ blameSince: Type2.Optional(Type2.String())
9406
9677
  }),
9407
9678
  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);
9679
+ const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, { ...params, metadataOnly: true });
9680
+ return text2(formatSearchResults(results), results);
9410
9681
  }
9411
9682
  });
9412
9683
  pi.registerTool({
9413
9684
  name: "find_similar",
9414
9685
  label: "Find Similar Code",
9415
9686
  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())
9687
+ parameters: Type2.Object({
9688
+ code: Type2.String({ description: "Code snippet to compare" }),
9689
+ limit: Type2.Optional(Type2.Number()),
9690
+ fileType: Type2.Optional(Type2.String()),
9691
+ directory: Type2.Optional(Type2.String()),
9692
+ chunkType: Type2.Optional(ChunkType),
9693
+ excludeFile: Type2.Optional(Type2.String())
9423
9694
  }),
9424
9695
  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);
9696
+ const results = await findSimilarCode(projectRoot2(ctx), HOST2, params.code, params);
9697
+ return text2(formatSearchResults(results, "similarity"), results);
9427
9698
  }
9428
9699
  });
9429
9700
  pi.registerTool({
9430
9701
  name: "implementation_lookup",
9431
9702
  label: "Implementation Lookup",
9432
9703
  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())
9704
+ parameters: Type2.Object({
9705
+ query: Type2.String(),
9706
+ limit: Type2.Optional(Type2.Number()),
9707
+ fileType: Type2.Optional(Type2.String()),
9708
+ directory: Type2.Optional(Type2.String())
9438
9709
  }),
9439
9710
  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);
9711
+ const results = await implementationLookup(projectRoot2(ctx), HOST2, params.query, params);
9712
+ return text2(formatDefinitionLookup(results, params.query), results);
9442
9713
  }
9443
9714
  });
9444
9715
  pi.registerTool({
9445
9716
  name: "index_codebase",
9446
9717
  label: "Index Codebase",
9447
9718
  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 }))
9719
+ parameters: Type2.Object({
9720
+ force: Type2.Optional(Type2.Boolean({ default: false })),
9721
+ estimateOnly: Type2.Optional(Type2.Boolean({ default: false })),
9722
+ verbose: Type2.Optional(Type2.Boolean({ default: false }))
9452
9723
  }),
9453
9724
  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);
9725
+ const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
9726
+ return result.kind === "estimate" ? text2(formatCostEstimate(result.estimate), result.estimate) : text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
9456
9727
  }
9457
9728
  });
9458
9729
  pi.registerTool({
9459
9730
  name: "index_status",
9460
9731
  label: "Index Status",
9461
9732
  description: "Check index health and current status.",
9462
- parameters: Type.Object({}),
9733
+ parameters: Type2.Object({}),
9463
9734
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9464
- const status = await getIndexStatus(projectRoot(ctx), HOST);
9465
- return text(formatStatus(status), status);
9735
+ const status = await getIndexStatus(projectRoot2(ctx), HOST2);
9736
+ return text2(formatStatus(status), status);
9466
9737
  }
9467
9738
  });
9468
9739
  pi.registerTool({
9469
9740
  name: "index_health_check",
9470
9741
  label: "Index Health Check",
9471
9742
  description: "Garbage collect orphaned embeddings/chunks and report health.",
9472
- parameters: Type.Object({}),
9743
+ parameters: Type2.Object({}),
9473
9744
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9474
- const result = await getIndexHealthCheck(projectRoot(ctx), HOST);
9475
- return text(formatHealthCheck(result), result);
9745
+ const result = await getIndexHealthCheck(projectRoot2(ctx), HOST2);
9746
+ return text2(formatHealthCheck(result), result);
9476
9747
  }
9477
9748
  });
9478
9749
  pi.registerTool({
9479
9750
  name: "index_metrics",
9480
9751
  label: "Index Metrics",
9481
9752
  description: "Return collected performance metrics when debug metrics are enabled.",
9482
- parameters: Type.Object({}),
9753
+ parameters: Type2.Object({}),
9483
9754
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9484
- const result = await getIndexMetrics(projectRoot(ctx), HOST);
9485
- return text(result.text, result);
9755
+ const result = await getIndexMetrics(projectRoot2(ctx), HOST2);
9756
+ return text2(result.text, result);
9486
9757
  }
9487
9758
  });
9488
9759
  pi.registerTool({
9489
9760
  name: "index_logs",
9490
9761
  label: "Index Logs",
9491
9762
  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")
9763
+ parameters: Type2.Object({
9764
+ limit: Type2.Optional(Type2.Number()),
9765
+ category: Type2.Optional(Type2.Union([
9766
+ Type2.Literal("search"),
9767
+ Type2.Literal("embedding"),
9768
+ Type2.Literal("cache"),
9769
+ Type2.Literal("gc"),
9770
+ Type2.Literal("branch"),
9771
+ Type2.Literal("general")
9501
9772
  ])),
9502
- level: Type.Optional(Type.Union([Type.Literal("error"), Type.Literal("warn"), Type.Literal("info"), Type.Literal("debug")]))
9773
+ level: Type2.Optional(Type2.Union([Type2.Literal("error"), Type2.Literal("warn"), Type2.Literal("info"), Type2.Literal("debug")]))
9503
9774
  }),
9504
9775
  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)
9518
- }),
9519
- 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);
9776
+ const result = await getIndexLogs(projectRoot2(ctx), HOST2, params);
9777
+ return text2(result.text, result);
9536
9778
  }
9537
9779
  });
9780
+ registerPiCallGraphTools(pi);
9538
9781
  pi.registerTool({
9539
9782
  name: "pr_impact",
9540
9783
  label: "PR Impact",
9541
9784
  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")]))
9785
+ parameters: Type2.Object({
9786
+ pr: Type2.Optional(Type2.Number()),
9787
+ branch: Type2.Optional(Type2.String()),
9788
+ maxDepth: Type2.Optional(Type2.Number({ default: 5 })),
9789
+ hubThreshold: Type2.Optional(Type2.Number({ default: 10 })),
9790
+ checkConflicts: Type2.Optional(Type2.Boolean({ default: false })),
9791
+ direction: Type2.Optional(Type2.Union([Type2.Literal("callers"), Type2.Literal("callees"), Type2.Literal("both")]))
9549
9792
  }),
9550
9793
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9551
- const result = await getPrImpact(projectRoot(ctx), HOST, params);
9552
- return text(formatPrImpact(result), result);
9794
+ const result = await getPrImpact(projectRoot2(ctx), HOST2, params);
9795
+ return text2(formatPrImpact(result), result);
9553
9796
  }
9554
9797
  });
9555
9798
  pi.registerTool({
9556
9799
  name: "knowledge_base_list",
9557
9800
  label: "List Knowledge Bases",
9558
9801
  description: "List configured knowledge-base paths included in the index.",
9559
- parameters: Type.Object({}),
9802
+ parameters: Type2.Object({}),
9560
9803
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
9561
- return text(listKnowledgeBases(projectRoot(ctx), HOST));
9804
+ return text2(listKnowledgeBases(projectRoot2(ctx), HOST2));
9562
9805
  }
9563
9806
  });
9564
9807
  pi.registerTool({
9565
9808
  name: "knowledge_base_add",
9566
9809
  label: "Add Knowledge Base",
9567
9810
  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" }) }),
9811
+ parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to add" }) }),
9569
9812
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9570
- return text(addKnowledgeBase(projectRoot(ctx), HOST, params.path));
9813
+ return text2(addKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9571
9814
  }
9572
9815
  });
9573
9816
  pi.registerTool({
9574
9817
  name: "knowledge_base_remove",
9575
9818
  label: "Remove Knowledge Base",
9576
9819
  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" }) }),
9820
+ parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to remove" }) }),
9578
9821
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
9579
- return text(removeKnowledgeBase(projectRoot(ctx), HOST, params.path));
9822
+ return text2(removeKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
9580
9823
  }
9581
9824
  });
9582
9825
  }